Commit 80ac24d7 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch...

Merge branch '235603-convert-group-members-list-view-from-haml-to-vue-wire-up-role-dropdown' into 'master'

Group members Vue conversion - Wire up role dropdown with Vuex action

See merge request gitlab-org/gitlab!45227
parents 417c3e11 7402c825
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { GlToast } from '@gitlab/ui';
import { parseDataAttributes } from 'ee_else_ce/groups/members/utils'; import { parseDataAttributes } from 'ee_else_ce/groups/members/utils';
import App from './components/app.vue'; import App from './components/app.vue';
import membersModule from '~/vuex_shared/modules/members'; import membersModule from '~/vuex_shared/modules/members';
...@@ -10,6 +11,7 @@ export const initGroupMembersApp = (el, tableFields, requestFormatter) => { ...@@ -10,6 +11,7 @@ export const initGroupMembersApp = (el, tableFields, requestFormatter) => {
} }
Vue.use(Vuex); Vue.use(Vuex);
Vue.use(GlToast);
const store = new Vuex.Store({ const store = new Vuex.Store({
...membersModule({ ...membersModule({
......
<script> <script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { mapActions } from 'vuex';
import { s__ } from '~/locale';
export default { export default {
name: 'RoleDropdown', name: 'RoleDropdown',
...@@ -17,14 +19,32 @@ export default { ...@@ -17,14 +19,32 @@ export default {
data() { data() {
return { return {
isDesktop: false, isDesktop: false,
busy: false,
}; };
}, },
mounted() { mounted() {
this.isDesktop = bp.isDesktop(); this.isDesktop = bp.isDesktop();
}, },
methods: { methods: {
handleSelect() { ...mapActions(['updateMemberRole']),
// Vuex action will be called here to make API request and update `member.accessLevel` handleSelect(value, name) {
if (value === this.member.accessLevel.integerValue) {
return;
}
this.busy = true;
this.updateMemberRole({
memberId: this.member.id,
accessLevel: { integerValue: value, stringValue: name },
})
.then(() => {
this.$toast.show(s__('Members|Role updated successfully.'));
this.busy = false;
})
.catch(() => {
this.busy = false;
});
}, },
}, },
}; };
...@@ -35,13 +55,14 @@ export default { ...@@ -35,13 +55,14 @@ export default {
:right="!isDesktop" :right="!isDesktop"
:text="member.accessLevel.stringValue" :text="member.accessLevel.stringValue"
:header-text="__('Change permissions')" :header-text="__('Change permissions')"
:disabled="busy"
> >
<gl-dropdown-item <gl-dropdown-item
v-for="(value, name) in member.validRoles" v-for="(value, name) in member.validRoles"
:key="value" :key="value"
is-check-item is-check-item
:is-checked="value === member.accessLevel.integerValue" :is-checked="value === member.accessLevel.integerValue"
@click="handleSelect" @click="handleSelect(value, name)"
> >
{{ name }} {{ name }}
</gl-dropdown-item> </gl-dropdown-item>
......
...@@ -16193,6 +16193,9 @@ msgstr "" ...@@ -16193,6 +16193,9 @@ msgstr ""
msgid "Members|No expiration set" msgid "Members|No expiration set"
msgstr "" msgstr ""
msgid "Members|Role updated successfully."
msgstr ""
msgid "Members|in %{time}" msgid "Members|in %{time}"
msgstr "" msgstr ""
......
import { mount, createWrapper } from '@vue/test-utils'; import { mount, createWrapper, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import { within } from '@testing-library/dom'; import { within } from '@testing-library/dom';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import waitForPromises from 'helpers/wait_for_promises';
import RoleDropdown from '~/vue_shared/components/members/table/role_dropdown.vue'; import RoleDropdown from '~/vue_shared/components/members/table/role_dropdown.vue';
import { member } from '../mock_data'; import { member } from '../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('RoleDropdown', () => { describe('RoleDropdown', () => {
let wrapper; let wrapper;
let actions;
const $toast = {
show: jest.fn(),
};
const createStore = () => {
actions = {
updateMemberRole: jest.fn(() => Promise.resolve()),
};
return new Vuex.Store({ actions });
};
const createComponent = (propsData = {}) => { const createComponent = (propsData = {}) => {
wrapper = mount(RoleDropdown, { wrapper = mount(RoleDropdown, {
...@@ -15,6 +32,11 @@ describe('RoleDropdown', () => { ...@@ -15,6 +32,11 @@ describe('RoleDropdown', () => {
member, member,
...propsData, ...propsData,
}, },
localVue,
store: createStore(),
mocks: {
$toast,
},
}); });
}; };
...@@ -22,7 +44,11 @@ describe('RoleDropdown', () => { ...@@ -22,7 +44,11 @@ describe('RoleDropdown', () => {
const getByTextInDropdownMenu = (text, options = {}) => const getByTextInDropdownMenu = (text, options = {}) =>
createWrapper(within(getDropdownMenu()).getByText(text, options)); createWrapper(within(getDropdownMenu()).getByText(text, options));
const getDropdownItemByText = text => const getDropdownItemByText = text =>
getByTextInDropdownMenu(text, { selector: '[role="menuitem"] p' }); createWrapper(
within(getDropdownMenu())
.getByText(text, { selector: '[role="menuitem"] p' })
.closest('[role="menuitem"]'),
);
const getCheckedDropdownItem = () => const getCheckedDropdownItem = () =>
wrapper wrapper
.findAll(GlDropdownItem) .findAll(GlDropdownItem)
...@@ -55,10 +81,47 @@ describe('RoleDropdown', () => { ...@@ -55,10 +81,47 @@ describe('RoleDropdown', () => {
expect(getByTextInDropdownMenu('Change permissions').exists()).toBe(true); expect(getByTextInDropdownMenu('Change permissions').exists()).toBe(true);
}); });
it('sets dropdown toggle and checks selected role', async () => { it('sets dropdown toggle and checks selected role', () => {
expect(findDropdownToggle().text()).toBe('Owner'); expect(findDropdownToggle().text()).toBe('Owner');
expect(getCheckedDropdownItem().text()).toBe('Owner'); expect(getCheckedDropdownItem().text()).toBe('Owner');
}); });
describe('when dropdown item is selected', () => {
it('does nothing if the item selected was already selected', () => {
getDropdownItemByText('Owner').trigger('click');
expect(actions.updateMemberRole).not.toHaveBeenCalled();
});
it('calls `updateMemberRole` Vuex action', () => {
getDropdownItemByText('Developer').trigger('click');
expect(actions.updateMemberRole).toHaveBeenCalledWith(expect.any(Object), {
memberId: member.id,
accessLevel: { integerValue: 30, stringValue: 'Developer' },
});
});
it('displays toast when successful', async () => {
getDropdownItemByText('Developer').trigger('click');
await waitForPromises();
expect($toast.show).toHaveBeenCalledWith('Role updated successfully.');
});
it('disables dropdown while waiting for `updateMemberRole` to resolve', async () => {
getDropdownItemByText('Developer').trigger('click');
await nextTick();
expect(findDropdown().attributes('disabled')).toBe('disabled');
await waitForPromises();
expect(findDropdown().attributes('disabled')).toBeUndefined();
});
});
}); });
it("sets initial dropdown toggle value to member's role", () => { it("sets initial dropdown toggle value to member's role", () => {
......
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