Commit 50580e04 authored by Martin Wortschack's avatar Martin Wortschack

Merge branch '238459-convert-group-members-view-source-component' into 'master'

Group members Vue conversion - source field

See merge request gitlab-org/gitlab!43457
parents 6fbf8eaf e82aa7b4
<script>
import { GlTooltipDirective } from '@gitlab/ui';
export default {
name: 'MemberSource',
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
memberSource: {
type: Object,
required: true,
},
isDirectMember: {
type: Boolean,
required: true,
},
},
};
</script>
<template>
<span v-if="isDirectMember">{{ __('Direct member') }}</span>
<a v-else v-gl-tooltip.hover :title="__('Inherited')" :href="memberSource.webUrl">{{
memberSource.name
}}</a>
</template>
......@@ -4,6 +4,7 @@ import { GlTable } from '@gitlab/ui';
import { FIELDS } from '../constants';
import initUserPopovers from '~/user_popovers';
import MemberAvatar from './member_avatar.vue';
import MemberSource from './member_source.vue';
import MembersTableCell from './members_table_cell.vue';
export default {
......@@ -12,6 +13,7 @@ export default {
GlTable,
MemberAvatar,
MembersTableCell,
MemberSource,
},
computed: {
...mapState(['members', 'tableFields']),
......@@ -43,8 +45,10 @@ export default {
</members-table-cell>
</template>
<template #cell(source)>
<!-- Temporarily empty -->
<template #cell(source)="{ item: member }">
<members-table-cell #default="{ isDirectMember }" :member="member">
<member-source :is-direct-member="isDirectMember" :member-source="member.source" />
</members-table-cell>
</template>
<template #head(actions)="{ label }">
......
<script>
import { mapState } from 'vuex';
import { MEMBER_TYPES } from '../constants';
export default {
......@@ -10,6 +11,7 @@ export default {
},
},
computed: {
...mapState(['sourceId']),
isGroup() {
return Boolean(this.member.sharedWithGroup);
},
......@@ -30,10 +32,14 @@ export default {
return MEMBER_TYPES.user;
},
isDirectMember() {
return this.member.source?.id === this.sourceId;
},
},
render() {
return this.$scopedSlots.default({
memberType: this.memberType,
isDirectMember: this.isDirectMember,
});
},
};
......
......@@ -8934,6 +8934,9 @@ msgstr ""
msgid "Diffs|Something went wrong while fetching diff lines."
msgstr ""
msgid "Direct member"
msgstr ""
msgid "Direction"
msgstr ""
......@@ -13614,6 +13617,9 @@ msgstr ""
msgid "Information about additional Pages templates and how to install them can be found in our %{pages_getting_started_guide}."
msgstr ""
msgid "Inherited"
msgstr ""
msgid "Inherited:"
msgstr ""
......
import { mount, createWrapper } from '@vue/test-utils';
import { getByText as getByTextHelper } from '@testing-library/dom';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import MemberSource from '~/vue_shared/components/members/table/member_source.vue';
describe('MemberSource', () => {
let wrapper;
const createComponent = propsData => {
wrapper = mount(MemberSource, {
propsData: {
memberSource: {
id: 102,
name: 'Foo bar',
webUrl: 'https://gitlab.com/groups/foo-bar',
},
...propsData,
},
directives: {
GlTooltip: createMockDirective(),
},
});
};
const getByText = (text, options) =>
createWrapper(getByTextHelper(wrapper.element, text, options));
const getTooltipDirective = elementWrapper => getBinding(elementWrapper.element, 'gl-tooltip');
afterEach(() => {
wrapper.destroy();
});
describe('direct member', () => {
it('displays "Direct member"', () => {
createComponent({
isDirectMember: true,
});
expect(getByText('Direct member').exists()).toBe(true);
});
});
describe('inherited member', () => {
let sourceGroupLink;
beforeEach(() => {
createComponent({
isDirectMember: false,
});
sourceGroupLink = getByText('Foo bar');
});
it('displays a link to source group', () => {
createComponent({
isDirectMember: false,
});
expect(sourceGroupLink.exists()).toBe(true);
expect(sourceGroupLink.attributes('href')).toBe('https://gitlab.com/groups/foo-bar');
});
it('displays tooltip with "Inherited"', () => {
const tooltipDirective = getTooltipDirective(sourceGroupLink);
expect(tooltipDirective).not.toBeUndefined();
expect(sourceGroupLink.attributes('title')).toBe('Inherited');
});
});
});
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { MEMBER_TYPES } from '~/vue_shared/components/members/constants';
import { member as memberMock, group, invite, accessRequest } from '../mock_data';
import MembersTableCell from '~/vue_shared/components/members/table/members_table_cell.vue';
......@@ -10,6 +11,10 @@ describe('MemberList', () => {
type: String,
required: true,
},
isDirectMember: {
type: Boolean,
required: true,
},
},
render(createElement) {
return createElement('div', this.memberType);
......@@ -17,20 +22,34 @@ describe('MemberList', () => {
};
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.component('wrapped-component', WrappedComponent);
const createStore = (state = {}) => {
return new Vuex.Store({
state: {
sourceId: 1,
...state,
},
});
};
let wrapper;
const createComponent = propsData => {
const createComponent = (propsData, state = {}) => {
wrapper = mount(MembersTableCell, {
localVue,
propsData,
store: createStore(state),
scopedSlots: {
default: '<wrapped-component :member-type="props.memberType" />',
default:
'<wrapped-component :member-type="props.memberType" :is-direct-member="props.isDirectMember" />',
},
});
};
const findWrappedComponent = () => wrapper.find(WrappedComponent);
afterEach(() => {
wrapper.destroy();
wrapper = null;
......@@ -47,7 +66,31 @@ describe('MemberList', () => {
({ member, expectedMemberType }) => {
createComponent({ member });
expect(wrapper.find(WrappedComponent).props('memberType')).toBe(expectedMemberType);
expect(findWrappedComponent().props('memberType')).toBe(expectedMemberType);
},
);
describe('isDirectMember', () => {
it('returns `true` when member source has same ID as `sourceId`', () => {
createComponent({
member: {
...memberMock,
source: {
...memberMock.source,
id: 1,
},
},
});
expect(findWrappedComponent().props('isDirectMember')).toBe(true);
});
it('returns `false` when member is inherited', () => {
createComponent({
member: memberMock,
});
expect(findWrappedComponent().props('isDirectMember')).toBe(false);
});
});
});
......@@ -5,7 +5,10 @@ import {
getByTestId as getByTestIdHelper,
} from '@testing-library/dom';
import MembersTable from '~/vue_shared/components/members/table/members_table.vue';
import MemberAvatar from '~/vue_shared/components/members/table/member_avatar.vue';
import MemberSource from '~/vue_shared/components/members/table/member_source.vue';
import * as initUserPopovers from '~/user_popovers';
import { member as memberMock, invite, accessRequest } from '../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -44,20 +47,31 @@ describe('MemberList', () => {
describe('fields', () => {
it.each`
field | label
${'source'} | ${'Source'}
${'granted'} | ${'Access granted'}
${'invited'} | ${'Invited'}
${'requested'} | ${'Requested'}
${'expires'} | ${'Access expires'}
${'maxRole'} | ${'Max role'}
${'expiration'} | ${'Expiration'}
`('renders the $label field', ({ field, label }) => {
field | label | member | expectedComponent
${'account'} | ${'Account'} | ${memberMock} | ${MemberAvatar}
${'source'} | ${'Source'} | ${memberMock} | ${MemberSource}
${'granted'} | ${'Access granted'} | ${memberMock} | ${null}
${'invited'} | ${'Invited'} | ${invite} | ${null}
${'requested'} | ${'Requested'} | ${accessRequest} | ${null}
${'expires'} | ${'Access expires'} | ${memberMock} | ${null}
${'maxRole'} | ${'Max role'} | ${memberMock} | ${null}
${'expiration'} | ${'Expiration'} | ${memberMock} | ${null}
`('renders the $label field', ({ field, label, member, expectedComponent }) => {
createComponent({
members: [member],
tableFields: [field],
});
expect(getByText(label, { selector: '[role="columnheader"]' }).exists()).toBe(true);
if (expectedComponent) {
expect(
wrapper
.find(`[data-label="${label}"][role="cell"]`)
.find(expectedComponent)
.exists(),
).toBe(true);
}
});
it('renders "Actions" field for screen readers', () => {
......
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