diff options
author | Benoit <benoit.gianinetti@sonarsource.com> | 2019-07-12 14:06:47 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2019-07-12 20:21:16 +0200 |
commit | 7f1afd8ce4723dad04762837efec3b4b2525dff5 (patch) | |
tree | 868fce46d90be48623e237c195a64e9aff091696 /server/sonar-web/src/main/js/apps/account/profile | |
parent | c2d9ced3637a5aa08422427e6435aaecaf659feb (diff) | |
download | sonarqube-7f1afd8ce4723dad04762837efec3b4b2525dff5.tar.gz sonarqube-7f1afd8ce4723dad04762837efec3b4b2525dff5.zip |
MMF-769 User can close their account (#1861)
Diffstat (limited to 'server/sonar-web/src/main/js/apps/account/profile')
10 files changed, 941 insertions, 0 deletions
diff --git a/server/sonar-web/src/main/js/apps/account/profile/Profile.tsx b/server/sonar-web/src/main/js/apps/account/profile/Profile.tsx index b752f8fa8b3..941371b710e 100644 --- a/server/sonar-web/src/main/js/apps/account/profile/Profile.tsx +++ b/server/sonar-web/src/main/js/apps/account/profile/Profile.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import { translate } from 'sonar-ui-common/helpers/l10n'; import UserExternalIdentity from './UserExternalIdentity'; +import UserDeleteAccount from './UserDeleteAccount'; import UserGroups from './UserGroups'; import UserScmAccounts from './UserScmAccounts'; import { isSonarCloud } from '../../../helpers/system'; @@ -59,6 +60,14 @@ export function Profile({ currentUser }: Props) { <hr /> <UserScmAccounts scmAccounts={currentUser.scmAccounts} user={currentUser} /> + + {isSonarCloud() && ( + <> + <hr /> + + <UserDeleteAccount user={currentUser} /> + </> + )} </div> </div> ); diff --git a/server/sonar-web/src/main/js/apps/account/profile/UserDeleteAccount.tsx b/server/sonar-web/src/main/js/apps/account/profile/UserDeleteAccount.tsx new file mode 100644 index 00000000000..38351f002b4 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/profile/UserDeleteAccount.tsx @@ -0,0 +1,125 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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 { translate } from 'sonar-ui-common/helpers/l10n'; +import { Button } from 'sonar-ui-common/components/controls/buttons'; +import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; +import UserDeleteAccountModal from './UserDeleteAccountModal'; +import UserDeleteAccountContent from './UserDeleteAccountContent'; +import { whenLoggedIn } from '../../../components/hoc/whenLoggedIn'; +import { withUserOrganizations } from '../../../components/hoc/withUserOrganizations'; +import { getOrganizationsThatPreventDeletion } from '../../../api/organizations'; + +interface Props { + user: T.LoggedInUser; + userOrganizations: T.Organization[]; +} + +interface State { + loading: boolean; + organizationsToTransferOrDelete: T.Organization[]; + showModal: boolean; +} + +export class UserDeleteAccount extends React.PureComponent<Props, State> { + mounted = false; + + state: State = { + loading: true, + organizationsToTransferOrDelete: [], + showModal: false + }; + + componentDidMount() { + this.mounted = true; + this.fetchOrganizationsThatPreventDeletion(); + } + + componentWillUnmount() { + this.mounted = false; + } + + fetchOrganizationsThatPreventDeletion = () => { + getOrganizationsThatPreventDeletion().then( + ({ organizations }) => { + if (this.mounted) { + this.setState({ + loading: false, + organizationsToTransferOrDelete: organizations + }); + } + }, + () => {} + ); + }; + + toggleModal = () => { + if (this.mounted) { + this.setState(state => ({ + showModal: !state.showModal + })); + } + }; + + render() { + const { user, userOrganizations } = this.props; + const { organizationsToTransferOrDelete, loading, showModal } = this.state; + + const label = translate('my_profile.delete_account'); + + return ( + <div> + <h2 className="spacer-bottom">{label}</h2> + + <DeferredSpinner loading={loading} /> + + {!loading && ( + <> + <UserDeleteAccountContent + className="list-styled no-padding big-spacer-top big-spacer-bottom" + organizationsSafeToDelete={userOrganizations} + organizationsToTransferOrDelete={organizationsToTransferOrDelete} + /> + + <Button + className="button-red" + disabled={organizationsToTransferOrDelete.length > 0} + onClick={this.toggleModal} + type="button"> + {translate('delete')} + </Button> + + {showModal && ( + <UserDeleteAccountModal + label={label} + organizationsSafeToDelete={userOrganizations} + organizationsToTransferOrDelete={organizationsToTransferOrDelete} + toggleModal={this.toggleModal} + user={user} + /> + )} + </> + )} + </div> + ); + } +} + +export default whenLoggedIn(withUserOrganizations(UserDeleteAccount)); diff --git a/server/sonar-web/src/main/js/apps/account/profile/UserDeleteAccountContent.tsx b/server/sonar-web/src/main/js/apps/account/profile/UserDeleteAccountContent.tsx new file mode 100644 index 00000000000..aaad598307b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/profile/UserDeleteAccountContent.tsx @@ -0,0 +1,148 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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 { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router'; +import { Alert } from 'sonar-ui-common/components/ui/Alert'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { getOrganizationUrl } from '../../../helpers/urls'; + +function getOrganizationLink(org: T.Organization, i: number, organizations: T.Organization[]) { + return ( + <span key={org.key}> + <Link to={getOrganizationUrl(org.key)}>{org.name}</Link> + {i < organizations.length - 1 && ', '} + </span> + ); +} + +export function ShowOrganizationsToTransferOrDelete({ + organizations +}: { + organizations: T.Organization[]; +}) { + return ( + <> + <p className="big-spacer-bottom"> + <FormattedMessage + defaultMessage={translate('my_profile.delete_account.info.orgs_to_transfer_or_delete')} + id="my_profile.delete_account.info.orgs_to_transfer_or_delete" + values={{ + organizations: <>{organizations.map(getOrganizationLink)}</> + }} + /> + </p> + + <Alert className="big-spacer-bottom" variant="warning"> + <FormattedMessage + defaultMessage={translate( + 'my_profile.delete_account.info.orgs_to_transfer_or_delete.info' + )} + id="my_profile.delete_account.info.orgs_to_transfer_or_delete.info" + values={{ + link: ( + <a + href="https://sieg.eu.ngrok.io/documentation/organizations/overview/#how-to-transfer-ownership-of-an-organization" + rel="noopener noreferrer" + target="_blank"> + {translate('my_profile.delete_account.info.orgs_to_transfer_or_delete.info.link')} + </a> + ) + }} + /> + </Alert> + </> + ); +} + +export function ShowOrganizations({ + className, + organizations +}: { + className?: string; + organizations: T.Organization[]; +}) { + const organizationsIAdministrate = organizations.filter(o => o.actions && o.actions.admin); + + return ( + <ul className={className}> + <li className="spacer-bottom">{translate('my_profile.delete_account.info')}</li> + + <li className="spacer-bottom"> + <FormattedMessage + defaultMessage={translate('my_profile.delete_account.data.info')} + id="my_profile.delete_account.data.info" + values={{ + help: ( + <a + href="/documentation/user-guide/user-account/#delete-your-user-account" + rel="noopener noreferrer" + target="_blank"> + {translate('learn_more')} + </a> + ) + }} + /> + </li> + + {organizations.length > 0 && ( + <li className="spacer-bottom"> + <FormattedMessage + defaultMessage={translate('my_profile.delete_account.info.orgs.members')} + id="my_profile.delete_account.info.orgs.members" + values={{ + organizations: <>{organizations.map(getOrganizationLink)}</> + }} + /> + </li> + )} + + {organizationsIAdministrate.length > 0 && ( + <li className="spacer-bottom"> + <FormattedMessage + defaultMessage={translate('my_profile.delete_account.info.orgs.administrators')} + id="my_profile.delete_account.info.orgs.administrators" + values={{ + organizations: <>{organizationsIAdministrate.map(getOrganizationLink)}</> + }} + /> + </li> + )} + </ul> + ); +} + +interface UserDeleteAccountContentProps { + className?: string; + organizationsSafeToDelete: T.Organization[]; + organizationsToTransferOrDelete: T.Organization[]; +} + +export default function UserDeleteAccountContent({ + className, + organizationsSafeToDelete, + organizationsToTransferOrDelete +}: UserDeleteAccountContentProps) { + if (organizationsToTransferOrDelete.length > 0) { + return <ShowOrganizationsToTransferOrDelete organizations={organizationsToTransferOrDelete} />; + } + + return <ShowOrganizations className={className} organizations={organizationsSafeToDelete} />; +} diff --git a/server/sonar-web/src/main/js/apps/account/profile/UserDeleteAccountModal.tsx b/server/sonar-web/src/main/js/apps/account/profile/UserDeleteAccountModal.tsx new file mode 100644 index 00000000000..566fad469f6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/profile/UserDeleteAccountModal.tsx @@ -0,0 +1,150 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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 { FormikProps } from 'formik'; +import { connect } from 'react-redux'; +import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; +import { Alert } from 'sonar-ui-common/components/ui/Alert'; +import InputValidationField from 'sonar-ui-common/components/controls/InputValidationField'; +import UserDeleteAccountContent from './UserDeleteAccountContent'; +import RecentHistory from '../../../app/components/RecentHistory'; +import ValidationModal from '../../../components/controls/ValidationModal'; +import { deactivateUser } from '../../../api/users'; +import { Router, withRouter } from '../../../components/hoc/withRouter'; +import { doLogout } from '../../../store/rootActions'; + +interface Values { + login: string; +} + +interface DeleteModalProps { + doLogout: () => Promise<void>; + label: string; + organizationsSafeToDelete: T.Organization[]; + organizationsToTransferOrDelete: T.Organization[]; + router: Pick<Router, 'push'>; + toggleModal: VoidFunction; + user: T.LoggedInUser; +} + +export class UserDeleteAccountModal extends React.PureComponent<DeleteModalProps> { + handleSubmit = () => { + const { user } = this.props; + + return deactivateUser({ login: user.login }) + .then(this.props.doLogout) + .then(() => { + RecentHistory.clear(); + window.location.replace('/account-deleted'); + }); + }; + + handleValidate = ({ login }: Values) => { + const { user } = this.props; + const errors: { login?: string } = {}; + const trimmedLogin = login.trim(); + + if (!trimmedLogin) { + errors.login = translate('my_profile.delete_account.login.required'); + } else if (user.externalIdentity && trimmedLogin !== user.externalIdentity.trim()) { + errors.login = translate('my_profile.delete_account.login.wrong_value'); + } + + return errors; + }; + + render() { + const { + label, + organizationsSafeToDelete, + organizationsToTransferOrDelete, + toggleModal, + user + } = this.props; + + return ( + <ValidationModal + confirmButtonText={translate('delete')} + header={translateWithParameters( + 'my_profile.delete_account.modal.header', + label, + user.externalIdentity || '' + )} + initialValues={{ + login: '' + }} + isDestructive={true} + onClose={toggleModal} + onSubmit={this.handleSubmit} + validate={this.handleValidate}> + {({ + dirty, + errors, + handleBlur, + handleChange, + isSubmitting, + touched, + values + }: FormikProps<Values>) => ( + <> + <Alert className="big-spacer-bottom" variant="error"> + {translate('my_profile.warning_message')} + </Alert> + + <UserDeleteAccountContent + className="list-styled no-padding big-spacer-bottom" + organizationsSafeToDelete={organizationsSafeToDelete} + organizationsToTransferOrDelete={organizationsToTransferOrDelete} + /> + + <InputValidationField + autoFocus={true} + dirty={dirty} + disabled={isSubmitting} + error={errors.login} + id="user-login" + label={ + <label htmlFor="user-login"> + {translate('my_profile.delete_account.verify')} + <em className="mandatory">*</em> + </label> + } + name="login" + onBlur={handleBlur} + onChange={handleChange} + touched={touched.login} + type="text" + value={values.login} + /> + </> + )} + </ValidationModal> + ); + } +} + +const mapStateToProps = () => ({}); + +const mapDispatchToProps = { doLogout: doLogout as any }; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(withRouter(UserDeleteAccountModal)); diff --git a/server/sonar-web/src/main/js/apps/account/profile/__tests__/UserDeleteAccount-test.tsx b/server/sonar-web/src/main/js/apps/account/profile/__tests__/UserDeleteAccount-test.tsx new file mode 100644 index 00000000000..1e4d4b217e3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/profile/__tests__/UserDeleteAccount-test.tsx @@ -0,0 +1,87 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import { click, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; +import { mockLoggedInUser, mockOrganization } from '../../../../helpers/testMocks'; +import { UserDeleteAccount } from '../UserDeleteAccount'; +import { getOrganizationsThatPreventDeletion } from '../../../../api/organizations'; + +jest.mock('../../../../api/organizations', () => ({ + getOrganizationsThatPreventDeletion: jest.fn().mockResolvedValue({ organizations: [] }) +})); + +beforeEach(() => { + jest.clearAllMocks(); +}); + +const organizationToTransferOrDelete = { + key: 'luke-leia', + name: 'Luke and Leia' +}; + +it('should render correctly', async () => { + const wrapper = shallowRender(); + expect(wrapper).toMatchSnapshot(); + + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); + + click(wrapper.find('Button')); + expect(wrapper).toMatchSnapshot(); +}); + +it('should get some organizations', async () => { + (getOrganizationsThatPreventDeletion as jest.Mock).mockResolvedValue({ + organizations: [organizationToTransferOrDelete] + }); + + const wrapper = shallowRender(); + + await waitAndUpdate(wrapper); + + expect(wrapper.state('loading')).toBeFalsy(); + expect(wrapper.state('organizationsToTransferOrDelete')).toEqual([ + organizationToTransferOrDelete + ]); + expect(getOrganizationsThatPreventDeletion).toBeCalled(); + expect(wrapper.find('Button').prop('disabled')).toBe(true); +}); + +it('should toggle modal', () => { + const wrapper = shallowRender(); + wrapper.setState({ loading: false }); + expect(wrapper.find('Connect(withRouter(UserDeleteAccountModal))').exists()).toBe(false); + click(wrapper.find('Button')); + expect(wrapper.find('Connect(withRouter(UserDeleteAccountModal))').exists()).toBe(true); +}); + +function shallowRender(props: Partial<UserDeleteAccount['props']> = {}) { + const user = mockLoggedInUser({ externalIdentity: 'luke' }); + + const userOrganizations = [ + mockOrganization({ key: 'luke-leia', name: 'Luke and Leia' }), + mockOrganization({ key: 'luke', name: 'Luke Skywalker' }) + ]; + + return shallow<UserDeleteAccount>( + <UserDeleteAccount user={user} userOrganizations={userOrganizations} {...props} /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/account/profile/__tests__/UserDeleteAccountContent-test.tsx b/server/sonar-web/src/main/js/apps/account/profile/__tests__/UserDeleteAccountContent-test.tsx new file mode 100644 index 00000000000..c9e5e334d5e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/profile/__tests__/UserDeleteAccountContent-test.tsx @@ -0,0 +1,71 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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 UserDeleteAccountContent, { + ShowOrganizations, + ShowOrganizationsToTransferOrDelete +} from '../UserDeleteAccountContent'; + +const organizationSafeToDelete = { + key: 'luke', + name: 'Luke Skywalker' +}; + +const organizationToTransferOrDelete = { + key: 'luke-leia', + name: 'Luke and Leia' +}; + +it('should render content correctly', () => { + expect( + shallow( + <UserDeleteAccountContent + className="my-class" + organizationsSafeToDelete={[organizationSafeToDelete]} + organizationsToTransferOrDelete={[organizationToTransferOrDelete]} + /> + ) + ).toMatchSnapshot(); + + expect( + shallow( + <UserDeleteAccountContent + className="my-class" + organizationsSafeToDelete={[organizationSafeToDelete]} + organizationsToTransferOrDelete={[]} + /> + ) + ).toMatchSnapshot(); +}); + +it('should render correctly ShowOrganizationsToTransferOrDelete', () => { + expect( + shallow( + <ShowOrganizationsToTransferOrDelete organizations={[organizationToTransferOrDelete]} /> + ) + ).toMatchSnapshot(); +}); + +it('should render correctly ShowOrganizations', () => { + expect( + shallow(<ShowOrganizations organizations={[organizationSafeToDelete]} />) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/account/profile/__tests__/UserDeleteAccountModal-test.tsx b/server/sonar-web/src/main/js/apps/account/profile/__tests__/UserDeleteAccountModal-test.tsx new file mode 100644 index 00000000000..e820b6c0284 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/profile/__tests__/UserDeleteAccountModal-test.tsx @@ -0,0 +1,86 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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 { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; +import { mockLoggedInUser, mockRouter } from '../../../../helpers/testMocks'; +import { UserDeleteAccountModal } from '../UserDeleteAccountModal'; +import { deactivateUser } from '../../../../api/users'; + +jest.mock('../../../../api/users', () => ({ + deactivateUser: jest.fn() +})); + +const organizationSafeToDelete = { + key: 'luke', + name: 'Luke Skywalker' +}; + +const organizationToTransferOrDelete = { + key: 'luke-leia', + name: 'Luke and Leia' +}; + +it('should render modal correctly', () => { + expect(shallowRender()).toMatchSnapshot(); +}); + +it('should handle submit', async () => { + (deactivateUser as jest.Mock).mockResolvedValue(true); + window.location.replace = jest.fn(); + + const wrapper = shallowRender(); + const instance = wrapper.instance(); + + instance.handleSubmit(); + await waitAndUpdate(wrapper); + + expect(deactivateUser).toBeCalled(); + expect(window.location.replace).toHaveBeenCalledWith('/account-deleted'); +}); + +it('should validate user input', () => { + const wrapper = shallowRender(); + const instance = wrapper.instance(); + const { handleValidate } = instance; + + expect(handleValidate({ login: '' }).login).toBe('my_profile.delete_account.login.required'); + expect(handleValidate({ login: 'abc' }).login).toBe( + 'my_profile.delete_account.login.wrong_value' + ); + expect(handleValidate({ login: 'luke' }).login).toBeUndefined(); +}); + +function shallowRender(props: Partial<UserDeleteAccountModal['props']> = {}) { + const user = mockLoggedInUser({ externalIdentity: 'luke' }); + + return shallow<UserDeleteAccountModal>( + <UserDeleteAccountModal + doLogout={jest.fn().mockResolvedValue(true)} + label="label" + organizationsSafeToDelete={[organizationSafeToDelete]} + organizationsToTransferOrDelete={[organizationToTransferOrDelete]} + router={mockRouter()} + toggleModal={jest.fn()} + user={user} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/account/profile/__tests__/__snapshots__/UserDeleteAccount-test.tsx.snap b/server/sonar-web/src/main/js/apps/account/profile/__tests__/__snapshots__/UserDeleteAccount-test.tsx.snap new file mode 100644 index 00000000000..05a4397a565 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/profile/__tests__/__snapshots__/UserDeleteAccount-test.tsx.snap @@ -0,0 +1,118 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<div> + <h2 + className="spacer-bottom" + > + my_profile.delete_account + </h2> + <DeferredSpinner + loading={true} + timeout={100} + /> +</div> +`; + +exports[`should render correctly 2`] = ` +<div> + <h2 + className="spacer-bottom" + > + my_profile.delete_account + </h2> + <DeferredSpinner + loading={false} + timeout={100} + /> + <UserDeleteAccountContent + className="list-styled no-padding big-spacer-top big-spacer-bottom" + organizationsSafeToDelete={ + Array [ + Object { + "key": "luke-leia", + "name": "Luke and Leia", + }, + Object { + "key": "luke", + "name": "Luke Skywalker", + }, + ] + } + organizationsToTransferOrDelete={Array []} + /> + <Button + className="button-red" + disabled={false} + onClick={[Function]} + type="button" + > + delete + </Button> +</div> +`; + +exports[`should render correctly 3`] = ` +<div> + <h2 + className="spacer-bottom" + > + my_profile.delete_account + </h2> + <DeferredSpinner + loading={false} + timeout={100} + /> + <UserDeleteAccountContent + className="list-styled no-padding big-spacer-top big-spacer-bottom" + organizationsSafeToDelete={ + Array [ + Object { + "key": "luke-leia", + "name": "Luke and Leia", + }, + Object { + "key": "luke", + "name": "Luke Skywalker", + }, + ] + } + organizationsToTransferOrDelete={Array []} + /> + <Button + className="button-red" + disabled={false} + onClick={[Function]} + type="button" + > + delete + </Button> + <Connect(withRouter(UserDeleteAccountModal)) + label="my_profile.delete_account" + organizationsSafeToDelete={ + Array [ + Object { + "key": "luke-leia", + "name": "Luke and Leia", + }, + Object { + "key": "luke", + "name": "Luke Skywalker", + }, + ] + } + organizationsToTransferOrDelete={Array []} + toggleModal={[Function]} + user={ + Object { + "externalIdentity": "luke", + "groups": Array [], + "isLoggedIn": true, + "login": "luke", + "name": "Skywalker", + "scmAccounts": Array [], + } + } + /> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/account/profile/__tests__/__snapshots__/UserDeleteAccountContent-test.tsx.snap b/server/sonar-web/src/main/js/apps/account/profile/__tests__/__snapshots__/UserDeleteAccountContent-test.tsx.snap new file mode 100644 index 00000000000..56ac708fb53 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/profile/__tests__/__snapshots__/UserDeleteAccountContent-test.tsx.snap @@ -0,0 +1,128 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render content correctly 1`] = ` +<ShowOrganizationsToTransferOrDelete + organizations={ + Array [ + Object { + "key": "luke-leia", + "name": "Luke and Leia", + }, + ] + } +/> +`; + +exports[`should render content correctly 2`] = ` +<ShowOrganizations + className="my-class" + organizations={ + Array [ + Object { + "key": "luke", + "name": "Luke Skywalker", + }, + ] + } +/> +`; + +exports[`should render correctly ShowOrganizations 1`] = ` +<ul> + <li + className="spacer-bottom" + > + my_profile.delete_account.info + </li> + <li + className="spacer-bottom" + > + <FormattedMessage + defaultMessage="my_profile.delete_account.data.info" + id="my_profile.delete_account.data.info" + values={ + Object { + "help": <a + href="/documentation/user-guide/user-account/#delete-your-user-account" + rel="noopener noreferrer" + target="_blank" + > + learn_more + </a>, + } + } + /> + </li> + <li + className="spacer-bottom" + > + <FormattedMessage + defaultMessage="my_profile.delete_account.info.orgs.members" + id="my_profile.delete_account.info.orgs.members" + values={ + Object { + "organizations": <React.Fragment> + <span> + <Link + onlyActiveOnIndex={false} + style={Object {}} + to="/organizations/luke" + > + Luke Skywalker + </Link> + </span> + </React.Fragment>, + } + } + /> + </li> +</ul> +`; + +exports[`should render correctly ShowOrganizationsToTransferOrDelete 1`] = ` +<Fragment> + <p + className="big-spacer-bottom" + > + <FormattedMessage + defaultMessage="my_profile.delete_account.info.orgs_to_transfer_or_delete" + id="my_profile.delete_account.info.orgs_to_transfer_or_delete" + values={ + Object { + "organizations": <React.Fragment> + <span> + <Link + onlyActiveOnIndex={false} + style={Object {}} + to="/organizations/luke-leia" + > + Luke and Leia + </Link> + </span> + </React.Fragment>, + } + } + /> + </p> + <Alert + className="big-spacer-bottom" + variant="warning" + > + <FormattedMessage + defaultMessage="my_profile.delete_account.info.orgs_to_transfer_or_delete.info" + id="my_profile.delete_account.info.orgs_to_transfer_or_delete.info" + values={ + Object { + "link": <a + href="https://sieg.eu.ngrok.io/documentation/organizations/overview/#how-to-transfer-ownership-of-an-organization" + rel="noopener noreferrer" + target="_blank" + > + my_profile.delete_account.info.orgs_to_transfer_or_delete.info.link + </a>, + } + } + /> + </Alert> +</Fragment> +`; diff --git a/server/sonar-web/src/main/js/apps/account/profile/__tests__/__snapshots__/UserDeleteAccountModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/account/profile/__tests__/__snapshots__/UserDeleteAccountModal-test.tsx.snap new file mode 100644 index 00000000000..5ba1d10e4ad --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/profile/__tests__/__snapshots__/UserDeleteAccountModal-test.tsx.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render modal correctly 1`] = ` +<ValidationModal + confirmButtonText="delete" + header="my_profile.delete_account.modal.header.label.luke" + initialValues={ + Object { + "login": "", + } + } + isDestructive={true} + onClose={[MockFunction]} + onSubmit={[Function]} + validate={[Function]} +> + <Component /> +</ValidationModal> +`; |