Commit ed7f44aa authored by Paul Slaughter's avatar Paul Slaughter

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
parent c50b0e58
<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 "" ...@@ -106,6 +106,9 @@ msgstr ""
msgid "%{counter_storage} (%{counter_repositories} repositories, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS)" msgid "%{counter_storage} (%{counter_repositories} repositories, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS)"
msgstr "" msgstr ""
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees" msgid "%{count} more assignees"
msgstr "" msgstr ""
...@@ -8175,6 +8178,9 @@ msgstr[1] "" ...@@ -8175,6 +8178,9 @@ msgstr[1] ""
msgid "should be higher than %{access} inherited membership from group %{group_name}" msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr "" msgstr ""
msgid "show less"
msgstr ""
msgid "source" msgid "source"
msgstr "" msgstr ""
......
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);
});
});
});
});
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