Create shared user-avatar-list component
**Why?** We need a component that can expand / collapse a list of avatars for approval rules table. See issue https://gitlab.com/gitlab-org/gitlab-ee/issues/1979
This commit is contained in:
parent
c50b0e58fe
commit
ed7f44aaba
3 changed files with 219 additions and 0 deletions
|
@ -0,0 +1,83 @@
|
|||
<script>
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { sprintf, __ } from '~/locale';
|
||||
import UserAvatarLink from './user_avatar_link.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
UserAvatarLink,
|
||||
GlButton,
|
||||
},
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
breakpoint: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 10,
|
||||
},
|
||||
imgSize: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 20,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isExpanded: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
visibleItems() {
|
||||
if (!this.hasHiddenItems) {
|
||||
return this.items;
|
||||
}
|
||||
|
||||
return this.items.slice(0, this.breakpoint);
|
||||
},
|
||||
hasHiddenItems() {
|
||||
return this.hasBreakpoint && !this.isExpanded && this.items.length > this.breakpoint;
|
||||
},
|
||||
hasBreakpoint() {
|
||||
return this.breakpoint > 0 && this.items.length > this.breakpoint;
|
||||
},
|
||||
expandText() {
|
||||
if (!this.hasHiddenItems) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const count = this.items.length - this.breakpoint;
|
||||
|
||||
return sprintf(__('%{count} more'), { count });
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
expand() {
|
||||
this.isExpanded = true;
|
||||
},
|
||||
collapse() {
|
||||
this.isExpanded = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<user-avatar-link
|
||||
v-for="item in visibleItems"
|
||||
:key="item.id"
|
||||
:link-href="item.web_url"
|
||||
:img-src="item.avatar_url"
|
||||
:img-alt="item.name"
|
||||
:tooltip-text="item.name"
|
||||
:img-size="imgSize"
|
||||
/>
|
||||
<template v-if="hasBreakpoint">
|
||||
<gl-button v-if="hasHiddenItems" variant="link" @click="expand"> {{ expandText }} </gl-button>
|
||||
<gl-button v-else variant="link" @click="collapse"> {{ __('show less') }} </gl-button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
|
@ -106,6 +106,9 @@ msgstr ""
|
|||
msgid "%{counter_storage} (%{counter_repositories} repositories, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS)"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{count} more"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{count} more assignees"
|
||||
msgstr ""
|
||||
|
||||
|
@ -8175,6 +8178,9 @@ msgstr[1] ""
|
|||
msgid "should be higher than %{access} inherited membership from group %{group_name}"
|
||||
msgstr ""
|
||||
|
||||
msgid "show less"
|
||||
msgstr ""
|
||||
|
||||
msgid "source"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { TEST_HOST } from 'spec/test_constants';
|
||||
import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue';
|
||||
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
|
||||
const TEST_IMAGE_SIZE = 7;
|
||||
const TEST_BREAKPOINT = 5;
|
||||
|
||||
const createUser = id => ({
|
||||
id,
|
||||
name: 'Lorem',
|
||||
web_url: `${TEST_HOST}/${id}`,
|
||||
avatar_url: `${TEST_HOST}/${id}/avatar`,
|
||||
});
|
||||
const createList = n =>
|
||||
Array(n)
|
||||
.fill(1)
|
||||
.map((x, id) => createUser(id));
|
||||
|
||||
const localVue = createLocalVue();
|
||||
|
||||
describe('UserAvatarList', () => {
|
||||
let propsData;
|
||||
let wrapper;
|
||||
|
||||
const factory = options => {
|
||||
wrapper = shallowMount(localVue.extend(UserAvatarList), {
|
||||
localVue,
|
||||
propsData,
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
const clickButton = () => {
|
||||
const button = wrapper.find(GlButton);
|
||||
button.vm.$emit('click');
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
propsData = { imgSize: TEST_IMAGE_SIZE };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('with no breakpoint', () => {
|
||||
beforeEach(() => {
|
||||
propsData.breakpoint = 0;
|
||||
});
|
||||
|
||||
it('renders avatars', () => {
|
||||
const items = createList(20);
|
||||
propsData.items = items;
|
||||
factory();
|
||||
|
||||
const links = wrapper.findAll(UserAvatarLink);
|
||||
const linkProps = links.wrappers.map(x => x.props());
|
||||
|
||||
expect(linkProps).toEqual(
|
||||
propsData.items.map(x =>
|
||||
jasmine.objectContaining({
|
||||
linkHref: x.web_url,
|
||||
imgSrc: x.avatar_url,
|
||||
imgAlt: x.name,
|
||||
tooltipText: x.name,
|
||||
imgSize: TEST_IMAGE_SIZE,
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with breakpoint and length equal to breakpoint', () => {
|
||||
beforeEach(() => {
|
||||
propsData.breakpoint = TEST_BREAKPOINT;
|
||||
propsData.items = createList(TEST_BREAKPOINT);
|
||||
});
|
||||
|
||||
it('renders all avatars if length is <= breakpoint', () => {
|
||||
factory();
|
||||
|
||||
const links = wrapper.findAll(UserAvatarLink);
|
||||
|
||||
expect(links.length).toEqual(propsData.items.length);
|
||||
});
|
||||
|
||||
it('does not show button', () => {
|
||||
factory();
|
||||
|
||||
expect(wrapper.find(GlButton).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with breakpoint and length greater than breakpoint', () => {
|
||||
beforeEach(() => {
|
||||
propsData.breakpoint = TEST_BREAKPOINT;
|
||||
propsData.items = createList(TEST_BREAKPOINT + 1);
|
||||
});
|
||||
|
||||
it('renders avatars up to breakpoint', () => {
|
||||
factory();
|
||||
|
||||
const links = wrapper.findAll(UserAvatarLink);
|
||||
|
||||
expect(links.length).toEqual(TEST_BREAKPOINT);
|
||||
});
|
||||
|
||||
describe('with expand clicked', () => {
|
||||
beforeEach(() => {
|
||||
factory();
|
||||
clickButton();
|
||||
});
|
||||
|
||||
it('renders all avatars', () => {
|
||||
const links = wrapper.findAll(UserAvatarLink);
|
||||
|
||||
expect(links.length).toEqual(propsData.items.length);
|
||||
});
|
||||
|
||||
it('with collapse clicked, it renders avatars up to breakpoint', () => {
|
||||
clickButton();
|
||||
const links = wrapper.findAll(UserAvatarLink);
|
||||
|
||||
expect(links.length).toEqual(TEST_BREAKPOINT);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue