Commit c468016c authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '229674-update-change-username-modal' into 'master'

Update change username to gl-modal

See merge request gitlab-org/gitlab!44325
parents 5d294320 88d94289
<script> <script>
/* eslint-disable vue/no-v-html */
import { escape } from 'lodash'; import { escape } from 'lodash';
import { GlButton } from '@gitlab/ui'; import { GlSafeHtmlDirective as SafeHtml, GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import { deprecatedCreateFlash as Flash } from '~/flash'; import { deprecatedCreateFlash as Flash } from '~/flash';
export default { export default {
components: { components: {
GlModal: DeprecatedModal2, GlModal,
GlButton, GlButton,
}, },
directives: {
GlModalDirective,
SafeHtml,
},
props: { props: {
actionUrl: { actionUrl: {
type: String, type: String,
...@@ -54,6 +56,21 @@ Please update your Git repository remotes as soon as possible.`), ...@@ -54,6 +56,21 @@ Please update your Git repository remotes as soon as possible.`),
false, false,
); );
}, },
primaryProps() {
return {
text: s__('Update username'),
attributes: [
{ variant: 'warning' },
{ category: 'primary' },
{ disabled: this.isRequestPending },
],
};
},
cancelProps() {
return {
text: s__('Cancel'),
};
},
}, },
methods: { methods: {
onConfirm() { onConfirm() {
...@@ -103,22 +120,21 @@ Please update your Git repository remotes as soon as possible.`), ...@@ -103,22 +120,21 @@ Please update your Git repository remotes as soon as possible.`),
<p class="form-text text-muted">{{ path }}</p> <p class="form-text text-muted">{{ path }}</p>
</div> </div>
<gl-button <gl-button
:data-target="`#${$options.modalId}`" v-gl-modal-directive="$options.modalId"
:disabled="isRequestPending || newUsername === username" :disabled="isRequestPending || newUsername === username"
category="primary" category="primary"
variant="warning" variant="warning"
data-toggle="modal" data-testid="username-change-confirmation-modal"
>{{ $options.buttonText }}</gl-button
> >
{{ $options.buttonText }}
</gl-button>
<gl-modal <gl-modal
:id="$options.modalId" :modal-id="$options.modalId"
:header-title-text="s__('Profiles|Change username') + '?'" :title="s__('Profiles|Change username') + '?'"
:footer-primary-button-text="$options.buttonText" :action-primary="primaryProps"
footer-primary-button-variant="warning" :action-cancel="cancelProps"
@submit="onConfirm" @primary="onConfirm"
> >
<span v-html="modalText"></span> <span v-safe-html="modalText"></span>
</gl-modal> </gl-modal>
</div> </div>
</template> </template>
---
title: Update change username modal
merge_request: 44325
author:
type: changed
...@@ -28386,6 +28386,9 @@ msgstr "" ...@@ -28386,6 +28386,9 @@ msgstr ""
msgid "Update now" msgid "Update now"
msgstr "" msgstr ""
msgid "Update username"
msgstr ""
msgid "Update variable" msgid "Update variable"
msgstr "" msgstr ""
......
...@@ -101,10 +101,10 @@ RSpec.describe 'Profile account page', :js do ...@@ -101,10 +101,10 @@ RSpec.describe 'Profile account page', :js do
it 'changes my username' do it 'changes my username' do
fill_in 'username-change-input', with: 'new-username' fill_in 'username-change-input', with: 'new-username'
page.find('[data-target="#username-change-confirmation-modal"]').click page.find('[data-testid="username-change-confirmation-modal"]').click
page.within('.modal') do page.within('.modal') do
find('.js-modal-primary-action').click find('.js-modal-action-primary').click
end end
expect(page).to have_content('new-username') expect(page).to have_content('new-username')
......
...@@ -128,10 +128,10 @@ def update_username(new_username) ...@@ -128,10 +128,10 @@ def update_username(new_username)
fill_in 'username-change-input', with: new_username fill_in 'username-change-input', with: new_username
page.find('[data-target="#username-change-confirmation-modal"]').click page.find('[data-testid="username-change-confirmation-modal"]').click
page.within('.modal') do page.within('.modal') do
find('.js-modal-primary-action').click find('.js-modal-action-primary').click
end end
wait_for_requests wait_for_requests
......
import Vue from 'vue'; import { shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import mountComponent from 'helpers/vue_mount_component_helper';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import updateUsername from '~/profile/account/components/update_username.vue'; import UpdateUsername from '~/profile/account/components/update_username.vue';
describe('UpdateUsername component', () => { describe('UpdateUsername component', () => {
const rootUrl = TEST_HOST; const rootUrl = TEST_HOST;
const actionUrl = `${TEST_HOST}/update/username`; const actionUrl = `${TEST_HOST}/update/username`;
const username = 'hasnoname'; const defaultProps = {
const newUsername = 'new_username'; actionUrl,
let Component; rootUrl,
let vm; initialUsername: 'hasnoname',
};
let wrapper;
let axiosMock; let axiosMock;
const createComponent = (props = {}) => {
wrapper = shallowMount(UpdateUsername, {
propsData: {
...defaultProps,
...props,
},
stubs: {
GlModal,
},
});
};
beforeEach(() => { beforeEach(() => {
axiosMock = new MockAdapter(axios); axiosMock = new MockAdapter(axios);
Component = Vue.extend(updateUsername); createComponent();
vm = mountComponent(Component, {
actionUrl,
rootUrl,
initialUsername: username,
});
}); });
afterEach(() => { afterEach(() => {
vm.$destroy(); wrapper.destroy();
axiosMock.restore(); axiosMock.restore();
}); });
const findElements = () => { const findElements = () => {
const modalSelector = `#${vm.$options.modalId}`; const modal = wrapper.find(GlModal);
return { return {
input: vm.$el.querySelector(`#${vm.$options.inputId}`), modal,
openModalBtn: vm.$el.querySelector(`[data-target="${modalSelector}"]`), input: wrapper.find(`#${wrapper.vm.$options.inputId}`),
modal: vm.$el.querySelector(modalSelector), openModalBtn: wrapper.find('[data-testid="username-change-confirmation-modal"]'),
modalBody: vm.$el.querySelector(`${modalSelector} .modal-body`), modalBody: modal.find('.modal-body'),
modalHeader: vm.$el.querySelector(`${modalSelector} .modal-title`), modalHeader: modal.find('.modal-title'),
confirmModalBtn: vm.$el.querySelector(`${modalSelector} .btn-warning`), confirmModalBtn: wrapper.find('.btn-warning'),
}; };
}; };
it('has a disabled button if the username was not changed', done => { it('has a disabled button if the username was not changed', async () => {
const { input, openModalBtn } = findElements(); const { openModalBtn } = findElements();
input.dispatchEvent(new Event('input'));
await wrapper.vm.$nextTick();
Vue.nextTick()
.then(() => { expect(openModalBtn.props('disabled')).toBe(true);
expect(vm.username).toBe(username);
expect(vm.newUsername).toBe(username);
expect(openModalBtn).toBeDisabled();
})
.then(done)
.catch(done.fail);
}); });
it('has an enabled button which if the username was changed', done => { it('has an enabled button which if the username was changed', async () => {
const { input, openModalBtn } = findElements(); const { input, openModalBtn } = findElements();
input.value = newUsername;
input.dispatchEvent(new Event('input'));
Vue.nextTick()
.then(() => {
expect(vm.username).toBe(username);
expect(vm.newUsername).toBe(newUsername);
expect(openModalBtn).not.toBeDisabled();
})
.then(done)
.catch(done.fail);
});
it('confirmation modal contains proper header and body', done => { input.element.value = 'newUsername';
const { modalBody, modalHeader } = findElements(); input.trigger('input');
vm.newUsername = newUsername; await wrapper.vm.$nextTick();
Vue.nextTick() expect(openModalBtn.props('disabled')).toBe(false);
.then(() => {
expect(modalHeader.textContent).toContain('Change username?');
expect(modalBody.textContent).toContain(
`You are going to change the username ${username} to ${newUsername}`,
);
})
.then(done)
.catch(done.fail);
}); });
it('confirmation modal should escape usernames properly', done => { describe('changing username', () => {
const { modalBody } = findElements(); const newUsername = 'new_username';
vm.username = '<i>Italic</i>'; beforeEach(async () => {
vm.newUsername = vm.username; createComponent();
wrapper.setData({ newUsername });
Vue.nextTick() await wrapper.vm.$nextTick();
.then(() => {
expect(modalBody.innerHTML).toContain('&lt;i&gt;Italic&lt;/i&gt;');
expect(modalBody.innerHTML).not.toContain(vm.username);
})
.then(done)
.catch(done.fail);
}); });
it('executes API call on confirmation button click', done => { it('confirmation modal contains proper header and body', async () => {
const { confirmModalBtn } = findElements(); const { modal } = findElements();
expect(modal.attributes('title')).toBe('Change username?');
expect(modal.text()).toContain(
`You are going to change the username ${defaultProps.initialUsername} to ${newUsername}`,
);
});
it('executes API call on confirmation button click', async () => {
axiosMock.onPut(actionUrl).replyOnce(() => [200, { message: 'Username changed' }]); axiosMock.onPut(actionUrl).replyOnce(() => [200, { message: 'Username changed' }]);
jest.spyOn(axios, 'put'); jest.spyOn(axios, 'put');
vm.newUsername = newUsername; await wrapper.vm.onConfirm();
await wrapper.vm.$nextTick();
Vue.nextTick()
.then(() => {
confirmModalBtn.click();
expect(axios.put).toHaveBeenCalledWith(actionUrl, { user: { username: newUsername } }); expect(axios.put).toHaveBeenCalledWith(actionUrl, { user: { username: newUsername } });
})
.then(done)
.catch(done.fail);
}); });
it('sets the username after a successful update', done => { it('sets the username after a successful update', async () => {
const { input, openModalBtn } = findElements(); const { input, openModalBtn } = findElements();
axiosMock.onPut(actionUrl).replyOnce(() => { axiosMock.onPut(actionUrl).replyOnce(() => {
expect(input).toBeDisabled(); expect(input.attributes('disabled')).toBe('disabled');
expect(openModalBtn).toBeDisabled(); expect(openModalBtn.props('disabled')).toBe(true);
return [200, { message: 'Username changed' }]; return [200, { message: 'Username changed' }];
}); });
vm.newUsername = newUsername; await wrapper.vm.onConfirm();
await wrapper.vm.$nextTick();
vm.onConfirm()
.then(() => { expect(input.attributes('disabled')).toBe(undefined);
expect(vm.username).toBe(newUsername); expect(openModalBtn.props('disabled')).toBe(true);
expect(vm.newUsername).toBe(newUsername);
expect(input).not.toBeDisabled();
expect(input.value).toBe(newUsername);
expect(openModalBtn).toBeDisabled();
})
.then(done)
.catch(done.fail);
}); });
it('does not set the username after a erroneous update', done => { it('does not set the username after a erroneous update', async () => {
const { input, openModalBtn } = findElements(); const { input, openModalBtn } = findElements();
axiosMock.onPut(actionUrl).replyOnce(() => { axiosMock.onPut(actionUrl).replyOnce(() => {
expect(input).toBeDisabled(); expect(input.attributes('disabled')).toBe('disabled');
expect(openModalBtn).toBeDisabled(); expect(openModalBtn.props('disabled')).toBe(true);
return [400, { message: 'Invalid username' }]; return [400, { message: 'Invalid username' }];
}); });
const invalidUsername = 'anything.git'; await expect(wrapper.vm.onConfirm()).rejects.toThrow();
vm.newUsername = invalidUsername; expect(input.attributes('disabled')).toBe(undefined);
expect(openModalBtn.props('disabled')).toBe(false);
vm.onConfirm() });
.then(() => done.fail('Expected onConfirm to throw!'))
.catch(() => {
expect(vm.username).toBe(username);
expect(vm.newUsername).toBe(invalidUsername);
expect(input).not.toBeDisabled();
expect(input.value).toBe(invalidUsername);
expect(openModalBtn).not.toBeDisabled();
})
.then(done)
.catch(done.fail);
}); });
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment