Merge branch 'ee1979-user-avatar-list-component' into 'master'
Create shared user-avatar-list component See merge request gitlab-org/gitlab-ce!24139
This commit is contained in:
		
						commit
						77de006319
					
				| 
						 | 
				
			
			@ -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 ""
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -8187,6 +8190,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 New Issue