summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Slaughter <pslaughter@gitlab.com>2019-01-02 13:09:54 -0600
committerPaul Slaughter <pslaughter@gitlab.com>2019-01-04 16:50:47 -0600
commited7f44aabab081fb1544d7160dd732645bd10261 (patch)
tree2fec55f6c213e4816503ac72cd0f0f4a197cf2db
parentc50b0e58feb208fd26129548ad086c3ef50df604 (diff)
downloadgitlab-ce-ee1979-user-avatar-list-component.tar.gz
Create shared user-avatar-list componentee1979-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
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue83
-rw-r--r--locale/gitlab.pot6
-rw-r--r--spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js130
3 files changed, 219 insertions, 0 deletions
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue
new file mode 100644
index 00000000000..7361867edc5
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue
@@ -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>
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ddfd5599883..5a7df199bb3 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js
new file mode 100644
index 00000000000..64aa7e29718
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js
@@ -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);
+ });
+ });
+ });
+});