From 4835b94eb4214cbd9be4b1e0c4c7209253c3f0fb Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Wed, 22 Aug 2018 13:33:15 +0200 Subject: [PATCH] drop project links from redux store (#632) --- .../sonar-web/src/main/js/api/projectLinks.ts | 11 +- server/sonar-web/src/main/js/app/types.ts | 2 +- .../src/main/js/app/utils/startReactApp.js | 2 +- .../main/js/apps/overview/meta/MetaLink.tsx | 2 +- .../main/js/apps/overview/meta/MetaLinks.tsx | 2 +- .../main/js/apps/project-admin/links/Links.js | 70 ---------- .../js/apps/project-admin/store/actions.js | 43 ------- .../apps/project-admin/store/rootReducer.js | 9 -- .../src/main/js/apps/projectLinks/App.tsx | 104 +++++++++++++++ .../links => projectLinks}/CreationModal.tsx | 8 +- .../links => projectLinks}/Header.tsx | 4 +- .../links => projectLinks}/LinkRow.tsx | 12 +- .../links/Table.js => projectLinks/Table.tsx} | 20 +-- .../apps/projectLinks/__tests__/App-test.tsx | 78 +++++++++++ .../__tests__/CreationModal-test.tsx | 37 ++++++ .../__tests__/Header-test.tsx} | 40 +++--- .../__tests__/LinkRow-test.tsx} | 44 ++++--- .../projectLinks/__tests__/Table-test.tsx | 36 ++++++ .../__tests__/__snapshots__/App-test.tsx.snap | 121 ++++++++++++++++++ .../__snapshots__/CreationModal-test.tsx.snap | 92 +++++++++++++ .../__snapshots__/Header-test.tsx.snap | 30 +++++ .../__snapshots__/LinkRow-test.tsx.snap | 99 ++++++++++++++ .../__snapshots__/Table-test.tsx.snap | 88 +++++++++++++ .../apps/projectLinks/__tests__/utils-test.ts | 40 ++++++ .../links/utils.js => projectLinks/utils.ts} | 14 +- .../src/main/js/store/rootReducer.js | 6 - 26 files changed, 808 insertions(+), 206 deletions(-) delete mode 100644 server/sonar-web/src/main/js/apps/project-admin/links/Links.js create mode 100644 server/sonar-web/src/main/js/apps/projectLinks/App.tsx rename server/sonar-web/src/main/js/apps/{project-admin/links => projectLinks}/CreationModal.tsx (92%) rename server/sonar-web/src/main/js/apps/{project-admin/links => projectLinks}/Header.tsx (95%) rename server/sonar-web/src/main/js/apps/{project-admin/links => projectLinks}/LinkRow.tsx (89%) rename server/sonar-web/src/main/js/apps/{project-admin/links/Table.js => projectLinks/Table.tsx} (81%) create mode 100644 server/sonar-web/src/main/js/apps/projectLinks/__tests__/App-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/projectLinks/__tests__/CreationModal-test.tsx rename server/sonar-web/src/main/js/apps/{project-admin/store/linksByProject.js => projectLinks/__tests__/Header-test.tsx} (50%) rename server/sonar-web/src/main/js/apps/{project-admin/store/links.js => projectLinks/__tests__/LinkRow-test.tsx} (58%) create mode 100644 server/sonar-web/src/main/js/apps/projectLinks/__tests__/Table-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/App-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/CreationModal-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/Header-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/LinkRow-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/Table-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/projectLinks/__tests__/utils-test.ts rename server/sonar-web/src/main/js/apps/{project-admin/links/utils.js => projectLinks/utils.ts} (72%) diff --git a/server/sonar-web/src/main/js/api/projectLinks.ts b/server/sonar-web/src/main/js/api/projectLinks.ts index 22c5fba17fd..eec3d0f4a72 100644 --- a/server/sonar-web/src/main/js/api/projectLinks.ts +++ b/server/sonar-web/src/main/js/api/projectLinks.ts @@ -29,9 +29,10 @@ export function deleteLink(linkId: string) { return post('/api/project_links/delete', { id: linkId }).catch(throwGlobalError); } -export function createLink(projectKey: string, name: string, url: string): Promise { - return postJSON('/api/project_links/create', { projectKey, name, url }).then( - r => r.link, - throwGlobalError - ); +export function createLink(data: { + name: string; + projectKey: string; + url: string; +}): Promise { + return postJSON('/api/project_links/create', data).then(r => r.link, throwGlobalError); } diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index 83eaf9592f7..476f2c07d28 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -482,7 +482,7 @@ export interface PermissionTemplate { export interface ProjectLink { id: string; - name: string; + name?: string; type: string; url: string; } diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.js b/server/sonar-web/src/main/js/app/utils/startReactApp.js index 788cd86620a..9b27ccbbb23 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.js +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.js @@ -224,7 +224,7 @@ const startReactApp = (lang, currentUser, appState) => { /> import('../../apps/project-admin/links/Links'))} + component={lazyLoad(() => import('../../apps/projectLinks/App'))} /> { - return this.props.createProjectLink(this.props.component.key, name, url); - }; - - handleDeleteLink = linkId => { - return this.props.deleteProjectLink(this.props.component.key, linkId); - }; - - render() { - return ( -
- -
- - - ); - } -} - -const mapStateToProps = (state, ownProps) => ({ - links: getProjectAdminProjectLinks(state, ownProps.location.query.id) -}); - -export default connect( - mapStateToProps, - { - fetchProjectLinks, - createProjectLink, - deleteProjectLink - } -)(Links); diff --git a/server/sonar-web/src/main/js/apps/project-admin/store/actions.js b/server/sonar-web/src/main/js/apps/project-admin/store/actions.js index 36eee2cfd81..669f1ffb359 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/store/actions.js +++ b/server/sonar-web/src/main/js/apps/project-admin/store/actions.js @@ -21,49 +21,6 @@ import { getProjectLinks, createLink, deleteLink } from '../../../api/projectLin import { getTree, changeKey as changeKeyApi } from '../../../api/components'; import throwGlobalError from '../../../app/utils/throwGlobalError'; -export const RECEIVE_PROJECT_LINKS = 'projectAdmin/RECEIVE_PROJECT_LINKS'; -export const receiveProjectLinks = (projectKey, links) => ({ - type: RECEIVE_PROJECT_LINKS, - projectKey, - links -}); - -export const fetchProjectLinks = projectKey => dispatch => { - getProjectLinks(projectKey).then( - links => { - dispatch(receiveProjectLinks(projectKey, links)); - }, - () => {} - ); -}; - -export const ADD_PROJECT_LINK = 'projectAdmin/ADD_PROJECT_LINK'; -const addProjectLink = (projectKey, link) => ({ - type: ADD_PROJECT_LINK, - projectKey, - link -}); - -export const createProjectLink = (projectKey, name, url) => dispatch => { - return createLink(projectKey, name, url).then(link => { - dispatch(addProjectLink(projectKey, link)); - }); -}; - -export const DELETE_PROJECT_LINK = 'projectAdmin/DELETE_PROJECT_LINK'; -export const deleteProjectLinkAction = (projectKey, linkId) => ({ - type: DELETE_PROJECT_LINK, - projectKey, - linkId -}); - -export function deleteProjectLink(projectKey, linkId) { - return dispatch => - deleteLink(linkId).then(() => { - dispatch(deleteProjectLinkAction(projectKey, linkId)); - }); -} - export const RECEIVE_PROJECT_MODULES = 'projectAdmin/RECEIVE_PROJECT_MODULES'; const receiveProjectModules = (projectKey, modules) => ({ type: RECEIVE_PROJECT_MODULES, diff --git a/server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js b/server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js index 34bf7739ac1..c00df0c9e03 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js +++ b/server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js @@ -18,8 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { combineReducers } from 'redux'; -import links, { getLink } from './links'; -import linksByProject, { getLinks } from './linksByProject'; import components, { getComponentByKey as nextGetComponentByKey } from './components'; import modulesByProject, { getProjectModules as nextGetProjectModules } from './modulesByProject'; import globalMessages, { @@ -27,8 +25,6 @@ import globalMessages, { } from '../../../store/globalMessages/duck'; const rootReducer = combineReducers({ - links, - linksByProject, components, modulesByProject, globalMessages @@ -36,11 +32,6 @@ const rootReducer = combineReducers({ export default rootReducer; -export const getLinkById = (state, linkId) => getLink(state.links, linkId); - -export const getProjectLinks = (state, projectKey) => - getLinks(state.linksByProject, projectKey).map(linkId => getLinkById(state, linkId)); - export const getComponentByKey = (state, componentKey) => nextGetComponentByKey(state.components, componentKey); diff --git a/server/sonar-web/src/main/js/apps/projectLinks/App.tsx b/server/sonar-web/src/main/js/apps/projectLinks/App.tsx new file mode 100644 index 00000000000..5e9d9a2b94e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectLinks/App.tsx @@ -0,0 +1,104 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import Helmet from 'react-helmet'; +import Header from './Header'; +import Table from './Table'; +import { getProjectLinks, createLink, deleteLink } from '../../api/projectLinks'; +import { ProjectLink, Component } from '../../app/types'; +import { translate } from '../../helpers/l10n'; +import DeferredSpinner from '../../components/common/DeferredSpinner'; + +interface Props { + component: Pick; +} + +interface State { + links?: ProjectLink[]; + loading: boolean; +} + +export default class App extends React.PureComponent { + mounted = false; + state: State = { loading: true }; + + componentDidMount() { + this.mounted = true; + this.fetchLinks(); + } + + componentDidUpdate(prevProps: Props) { + if (prevProps.component.key !== this.props.component.key) { + this.fetchLinks(); + } + } + + componentWillUnmount() { + this.mounted = false; + } + + fetchLinks = () => { + this.setState({ loading: true }); + getProjectLinks(this.props.component.key).then( + links => { + if (this.mounted) { + this.setState({ links, loading: false }); + } + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + }; + + handleCreateLink = (name: string, url: string) => { + return createLink({ name, projectKey: this.props.component.key, url }).then(link => { + if (this.mounted) { + this.setState(({ links = [] }) => ({ + links: [...links, link] + })); + } + }); + }; + + handleDeleteLink = (linkId: string) => { + return deleteLink(linkId).then(() => { + if (this.mounted) { + this.setState(({ links = [] }) => ({ + links: links.filter(link => link.id !== linkId) + })); + } + }); + }; + + render() { + return ( +
+ +
+ + {this.state.links &&
} + + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/CreationModal.tsx b/server/sonar-web/src/main/js/apps/projectLinks/CreationModal.tsx similarity index 92% rename from server/sonar-web/src/main/js/apps/project-admin/links/CreationModal.tsx rename to server/sonar-web/src/main/js/apps/projectLinks/CreationModal.tsx index c87ae12e438..81a1361725a 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/links/CreationModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectLinks/CreationModal.tsx @@ -18,10 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import DeferredSpinner from '../../../components/common/DeferredSpinner'; -import SimpleModal from '../../../components/controls/SimpleModal'; -import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; -import { translate } from '../../../helpers/l10n'; +import DeferredSpinner from '../../components/common/DeferredSpinner'; +import SimpleModal from '../../components/controls/SimpleModal'; +import { SubmitButton, ResetButtonLink } from '../../components/ui/buttons'; +import { translate } from '../../helpers/l10n'; interface Props { onClose: () => void; diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/Header.tsx b/server/sonar-web/src/main/js/apps/projectLinks/Header.tsx similarity index 95% rename from server/sonar-web/src/main/js/apps/project-admin/links/Header.tsx rename to server/sonar-web/src/main/js/apps/projectLinks/Header.tsx index b8e07c56fbb..9cc71970886 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/links/Header.tsx +++ b/server/sonar-web/src/main/js/apps/projectLinks/Header.tsx @@ -19,8 +19,8 @@ */ import * as React from 'react'; import CreationModal from './CreationModal'; -import { Button } from '../../../components/ui/buttons'; -import { translate } from '../../../helpers/l10n'; +import { Button } from '../../components/ui/buttons'; +import { translate } from '../../helpers/l10n'; interface Props { onCreate: (name: string, url: string) => Promise; diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/LinkRow.tsx b/server/sonar-web/src/main/js/apps/projectLinks/LinkRow.tsx similarity index 89% rename from server/sonar-web/src/main/js/apps/project-admin/links/LinkRow.tsx rename to server/sonar-web/src/main/js/apps/projectLinks/LinkRow.tsx index 14725ed3c59..7d2630cba14 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/links/LinkRow.tsx +++ b/server/sonar-web/src/main/js/apps/projectLinks/LinkRow.tsx @@ -19,11 +19,11 @@ */ import * as React from 'react'; import { isProvided, getLinkName } from './utils'; -import { ProjectLink } from '../../../app/types'; -import ConfirmButton from '../../../components/controls/ConfirmButton'; -import ProjectLinkIcon from '../../../components/icons-components/ProjectLinkIcon'; -import { Button } from '../../../components/ui/buttons'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { ProjectLink } from '../../app/types'; +import ConfirmButton from '../../components/controls/ConfirmButton'; +import ProjectLinkIcon from '../../components/icons-components/ProjectLinkIcon'; +import { Button } from '../../components/ui/buttons'; +import { translate, translateWithParameters } from '../../helpers/l10n'; interface Props { link: ProjectLink; @@ -71,7 +71,7 @@ export default class LinkRow extends React.PureComponent { isDestructive={true} modalBody={translateWithParameters( 'project_links.are_you_sure_to_delete_x_link', - link.name + link.name! )} modalHeader={translate('project_links.delete_project_link')} onConfirm={this.props.onDelete}> diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/Table.js b/server/sonar-web/src/main/js/apps/projectLinks/Table.tsx similarity index 81% rename from server/sonar-web/src/main/js/apps/project-admin/links/Table.js rename to server/sonar-web/src/main/js/apps/projectLinks/Table.tsx index 8c8c80788fb..41a21af6d92 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/links/Table.js +++ b/server/sonar-web/src/main/js/apps/projectLinks/Table.tsx @@ -17,18 +17,18 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; -import PropTypes from 'prop-types'; +import * as React from 'react'; import LinkRow from './LinkRow'; import { orderLinks } from './utils'; -import { translate } from '../../../helpers/l10n'; +import { ProjectLink } from '../../app/types'; +import { translate } from '../../helpers/l10n'; -export default class Table extends React.PureComponent { - static propTypes = { - links: PropTypes.array.isRequired, - onDelete: PropTypes.func.isRequired - }; +interface Props { + links: ProjectLink[]; + onDelete: (linkId: string) => Promise; +} +export default class Table extends React.PureComponent { renderHeader() { // keep empty cell for actions return ( @@ -43,6 +43,10 @@ export default class Table extends React.PureComponent { } render() { + if (!this.props.links.length) { + return
{translate('no_results')}
; + } + const orderedLinks = orderLinks(this.props.links); const linkRows = orderedLinks.map(link => ( diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/App-test.tsx new file mode 100644 index 00000000000..c6fcd752a5f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/App-test.tsx @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import App from '../App'; +import { waitAndUpdate } from '../../../helpers/testUtils'; +import { createLink, deleteLink, getProjectLinks } from '../../../api/projectLinks'; + +// import { getProjectLinks, createLink, deleteLink } from '../../api/projectLinks'; +jest.mock('../../../api/projectLinks', () => ({ + getProjectLinks: jest + .fn() + .mockResolvedValue([ + { id: '1', type: 'homepage', url: 'http://example.com' }, + { id: '2', name: 'foo', type: 'foo', url: 'http://example.com/foo' } + ]), + createLink: jest + .fn() + .mockResolvedValue({ id: '3', name: 'bar', type: 'bar', url: 'http://example.com/bar' }), + deleteLink: jest.fn().mockResolvedValue(undefined) +})); + +it('should fetch links and render', async () => { + const wrapper = shallow(); + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); + expect(getProjectLinks).toBeCalledWith('comp'); +}); + +it('should fetch links when component changes', async () => { + const wrapper = shallow(); + await waitAndUpdate(wrapper); + expect(getProjectLinks).lastCalledWith('comp'); + + wrapper.setProps({ component: { key: 'another' } }); + expect(getProjectLinks).lastCalledWith('another'); +}); + +it('should create link', async () => { + const wrapper = shallow(); + await waitAndUpdate(wrapper); + + wrapper.find('Header').prop('onCreate')('bar', 'http://example.com/bar'); + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); + expect(createLink).toBeCalledWith({ + name: 'bar', + projectKey: 'comp', + url: 'http://example.com/bar' + }); +}); + +it('should delete link', async () => { + const wrapper = shallow(); + await waitAndUpdate(wrapper); + + wrapper.find('Table').prop('onDelete')('foo'); + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); + expect(deleteLink).toBeCalledWith('foo'); +}); diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/CreationModal-test.tsx b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/CreationModal-test.tsx new file mode 100644 index 00000000000..99160f81695 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/CreationModal-test.tsx @@ -0,0 +1,37 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import CreationModal from '../CreationModal'; +import { change, submit } from '../../../helpers/testUtils'; + +it('should create link', () => { + const onClose = jest.fn(); + const onSubmit = jest.fn().mockResolvedValue(undefined); + const wrapper = shallow(); + const form = wrapper.dive(); + + change(form.find('#create-link-name'), 'foo'); + change(form.find('#create-link-url'), 'http://example.com/foo'); + expect(form).toMatchSnapshot(); + + submit(wrapper); + expect(onSubmit).toBeCalledWith('foo', 'http://example.com/foo'); +}); diff --git a/server/sonar-web/src/main/js/apps/project-admin/store/linksByProject.js b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/Header-test.tsx similarity index 50% rename from server/sonar-web/src/main/js/apps/project-admin/store/linksByProject.js rename to server/sonar-web/src/main/js/apps/projectLinks/__tests__/Header-test.tsx index d26a10e0a2a..7b798e15084 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/store/linksByProject.js +++ b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/Header-test.tsx @@ -17,29 +17,25 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { without } from 'lodash'; -import { RECEIVE_PROJECT_LINKS, DELETE_PROJECT_LINK, ADD_PROJECT_LINK } from './actions'; +import * as React from 'react'; +import { shallow } from 'enzyme'; +import Header from '../Header'; +import { click } from '../../../helpers/testUtils'; -const linksByProject = (state = {}, action = {}) => { - if (action.type === RECEIVE_PROJECT_LINKS) { - const linkIds = action.links.map(link => link.id); - return { ...state, [action.projectKey]: linkIds }; - } +it('should render', () => { + expect(shallow(
)).toMatchSnapshot(); +}); - if (action.type === ADD_PROJECT_LINK) { - const byProject = state[action.projectKey] || []; - const ids = [...byProject, action.link.id]; - return { ...state, [action.projectKey]: ids }; - } +it('should open creation modal', () => { + const onCreate = jest.fn(); + const wrapper = shallow(
); + click(wrapper.find('Button')); + expect(wrapper.find('CreationModal').exists()).toBe(true); - if (action.type === DELETE_PROJECT_LINK) { - const ids = without(state[action.projectKey], action.linkId); - return { ...state, [action.projectKey]: ids }; - } + wrapper.find('CreationModal').prop('onSubmit')('foo', 'http://example.com/foo'); + expect(onCreate).toBeCalledWith('foo', 'http://example.com/foo'); - return state; -}; - -export default linksByProject; - -export const getLinks = (state, projectKey) => state[projectKey] || []; + wrapper.find('CreationModal').prop('onClose')(); + wrapper.update(); + expect(wrapper.find('CreationModal').exists()).toBe(false); +}); diff --git a/server/sonar-web/src/main/js/apps/project-admin/store/links.js b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/LinkRow-test.tsx similarity index 58% rename from server/sonar-web/src/main/js/apps/project-admin/store/links.js rename to server/sonar-web/src/main/js/apps/projectLinks/__tests__/LinkRow-test.tsx index dda15e1aef5..19cff23dd62 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/store/links.js +++ b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/LinkRow-test.tsx @@ -17,26 +17,28 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { keyBy, omit } from 'lodash'; -import { RECEIVE_PROJECT_LINKS, DELETE_PROJECT_LINK, ADD_PROJECT_LINK } from './actions'; +import * as React from 'react'; +import { shallow } from 'enzyme'; +import LinkRow from '../LinkRow'; -const links = (state = {}, action = {}) => { - if (action.type === RECEIVE_PROJECT_LINKS) { - const newLinksById = keyBy(action.links, 'id'); - return { ...state, ...newLinksById }; - } +it('should render provided link', () => { + expect( + shallow( + + ) + ).toMatchSnapshot(); +}); - if (action.type === ADD_PROJECT_LINK) { - return { ...state, [action.link.id]: action.link }; - } - - if (action.type === DELETE_PROJECT_LINK) { - return omit(state, action.linkId); - } - - return state; -}; - -export default links; - -export const getLink = (state, id) => state[id]; +it('should render custom link', () => { + expect( + shallow( + + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/Table-test.tsx b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/Table-test.tsx new file mode 100644 index 00000000000..aeddcd78524 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/Table-test.tsx @@ -0,0 +1,36 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import Table from '../Table'; + +it('should render', () => { + const links = [ + { id: '1', type: 'homepage', url: 'http://example.com/homepage' }, + { id: '2', type: 'issue', url: 'http://example.com/issue' }, + { id: '3', name: 'foo', type: 'foo', url: 'http://example.com/foo' }, + { id: '4', name: 'bar', type: 'bar', url: 'http://example.com/bar' } + ]; + expect(shallow(
)).toMatchSnapshot(); +}); + +it('should render empty', () => { + expect(shallow(
)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/App-test.tsx.snap new file mode 100644 index 00000000000..b817f3fbcfd --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/App-test.tsx.snap @@ -0,0 +1,121 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should create link 1`] = ` +
+ +
+ +
+ + +`; + +exports[`should delete link 1`] = ` +
+ +
+ +
+ + +`; + +exports[`should fetch links and render 1`] = ` +
+ +
+ +
+ + +`; diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/CreationModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/CreationModal-test.tsx.snap new file mode 100644 index 00000000000..b56b44cef77 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/CreationModal-test.tsx.snap @@ -0,0 +1,92 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should create link 1`] = ` + +
+
+

+ project_links.create_new_project_link +

+
+
+
+ + +
+
+ + +
+
+
+ + + create + + + cancel + +
+ +
+`; diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/Header-test.tsx.snap new file mode 100644 index 00000000000..073d2a845a1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/Header-test.tsx.snap @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` + +
+

+ project_links.page +

+
+ +
+
+ project_links.page.description +
+
+
+`; diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/LinkRow-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/LinkRow-test.tsx.snap new file mode 100644 index 00000000000..cea1ad2384f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/LinkRow-test.tsx.snap @@ -0,0 +1,99 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render custom link 1`] = ` + + + + + +`; + +exports[`should render provided link 1`] = ` + + + + +`; diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/Table-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/Table-test.tsx.snap new file mode 100644 index 00000000000..c043f6a5f56 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/Table-test.tsx.snap @@ -0,0 +1,88 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` +
+
+
+ +
+ + foo + +
+
+
+ + http://example.com + + + +
+
+ +
+
+ + project_links.homepage + +
+
+ + sonar.links.homepage + +
+
+
+
+ + http://example.com + + +
+ + + + + + + + + + + + + + +
+`; + +exports[`should render empty 1`] = ` +
+ no_results +
+`; diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/utils-test.ts new file mode 100644 index 00000000000..882d0064c16 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/utils-test.ts @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as utils from '../utils'; + +it('#isProvided', () => { + expect(utils.isProvided({ type: 'homepage' })).toBe(true); + expect(utils.isProvided({ type: 'custom' })).toBe(false); +}); + +it('#orderLinks', () => { + const homepage = { type: 'homepage' }; + const issues = { type: 'issue' }; + const foo = { name: 'foo', type: 'foo' }; + const bar = { name: 'bar', type: 'bar' }; + expect(utils.orderLinks([foo, homepage, issues, bar])).toEqual([homepage, issues, bar, foo]); + expect(utils.orderLinks([foo, bar])).toEqual([bar, foo]); + expect(utils.orderLinks([issues, homepage])).toEqual([homepage, issues]); +}); + +it('#getLinkName', () => { + expect(utils.getLinkName({ type: 'homepage' })).toBe('project_links.homepage'); + expect(utils.getLinkName({ name: 'foo', type: 'custom' })).toBe('foo'); +}); diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/utils.js b/server/sonar-web/src/main/js/apps/projectLinks/utils.ts similarity index 72% rename from server/sonar-web/src/main/js/apps/project-admin/links/utils.js rename to server/sonar-web/src/main/js/apps/projectLinks/utils.ts index 06539348fef..2bb1bd70c25 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/links/utils.js +++ b/server/sonar-web/src/main/js/apps/projectLinks/utils.ts @@ -18,22 +18,24 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { partition, sortBy } from 'lodash'; -import { translate } from '../../../helpers/l10n'; +import { ProjectLink } from '../../app/types'; +import { translate } from '../../helpers/l10n'; const PROVIDED_TYPES = ['homepage', 'ci', 'issue', 'scm', 'scm_dev']; +type NameAndType = Pick; -export function isProvided(link) { +export function isProvided(link: Pick) { return PROVIDED_TYPES.includes(link.type); } -export function orderLinks(links) { - const [provided, unknown] = partition(links, isProvided); +export function orderLinks(links: T[]) { + const [provided, unknown] = partition(links, isProvided); return [ ...sortBy(provided, link => PROVIDED_TYPES.indexOf(link.type)), - ...sortBy(unknown, link => link.name.toLowerCase()) + ...sortBy(unknown, link => link.name!.toLowerCase()) ]; } -export function getLinkName(link) { +export function getLinkName(link: NameAndType) { return isProvided(link) ? translate('project_links', link.type) : link.name; } diff --git a/server/sonar-web/src/main/js/store/rootReducer.js b/server/sonar-web/src/main/js/store/rootReducer.js index 21e8c828500..8bec4555692 100644 --- a/server/sonar-web/src/main/js/store/rootReducer.js +++ b/server/sonar-web/src/main/js/store/rootReducer.js @@ -134,12 +134,6 @@ export const getSettingsAppEncryptionState = state => export const getSettingsAppGlobalMessages = state => fromSettingsApp.getGlobalMessages(state.settingsApp); -export const getProjectAdminLinkById = (state, linkId) => - fromProjectAdminApp.getLinkById(state.projectAdminApp, linkId); - -export const getProjectAdminProjectLinks = (state, projectKey) => - fromProjectAdminApp.getProjectLinks(state.projectAdminApp, projectKey); - export const getProjectAdminComponentByKey = (state, componentKey) => fromProjectAdminApp.getComponentByKey(state.projectAdminApp, componentKey); -- 2.39.5