Commit 5649aa73 authored by Robert Hunt's avatar Robert Hunt

Merge branch '335514-use-gl-avatar-project-list-selector' into 'master'

Use GlAvatar in project_list_item component

See merge request gitlab-org/gitlab!83806
parents e33b1622 be8ec236
<script>
import Identicon from '../identicon.vue';
import ProjectAvatarImage from './image.vue';
export default {
name: 'DeprecatedProjectAvatar',
components: {
Identicon,
ProjectAvatarImage,
},
props: {
project: {
type: Object,
required: true,
},
size: {
type: Number,
default: 40,
required: false,
},
},
computed: {
sizeClass() {
return `s${this.size}`;
},
},
};
</script>
<template>
<span :class="sizeClass" class="avatar-container rect-avatar project-avatar">
<project-avatar-image
v-if="project.avatar_url"
:link-href="project.path"
:img-src="project.avatar_url"
:img-alt="project.name"
:img-size="size"
/>
<identicon
v-else
:entity-id="project.id"
:entity-name="project.name"
:size-class="sizeClass"
class="rect-avatar"
/>
</span>
</template>
<script>
/* This is a re-usable vue component for rendering a project avatar that
does not need to link to the project's profile. The image and an optional
tooltip can be configured by props passed to this component.
Sample configuration:
<project-avatar-image
:lazy="true"
:img-src="projectAvatarSrc"
:img-alt="tooltipText"
:tooltip-text="tooltipText"
tooltip-placement="top"
/>
*/
import defaultAvatarUrl from 'images/no_avatar.png';
import { __ } from '~/locale';
import { placeholderImage } from '../../../lazy_loader';
export default {
name: 'ProjectAvatarImage',
props: {
lazy: {
type: Boolean,
required: false,
default: false,
},
imgSrc: {
type: String,
required: false,
default: defaultAvatarUrl,
},
cssClasses: {
type: String,
required: false,
default: '',
},
imgAlt: {
type: String,
required: false,
default: __('project avatar'),
},
size: {
type: Number,
required: false,
default: 20,
},
},
computed: {
// API response sends null when gravatar is disabled and
// we provide an empty string when we use it inside project avatar link.
// In both cases we should render the defaultAvatarUrl
sanitizedSource() {
return this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc;
},
resultantSrcAttribute() {
return this.lazy ? placeholderImage : this.sanitizedSource;
},
avatarSizeClass() {
return `s${this.size}`;
},
},
};
</script>
<template>
<img
:class="{
lazy: lazy,
[avatarSizeClass]: true,
[cssClasses]: true,
}"
:src="resultantSrcAttribute"
:width="size"
:height="size"
:alt="imgAlt"
:data-src="sanitizedSource"
class="avatar"
/>
</template>
<script>
import { getIdenticonBackgroundClass, getIdenticonTitle } from '~/helpers/avatar_helper';
export default {
props: {
entityId: {
type: [Number, String],
required: true,
},
entityName: {
type: String,
required: true,
},
sizeClass: {
type: String,
required: false,
default: 's40',
},
},
computed: {
identiconBackgroundClass() {
return getIdenticonBackgroundClass(this.entityId);
},
identiconTitle() {
return getIdenticonTitle(this.entityName);
},
},
};
</script>
<template>
<div ref="identicon" :class="[sizeClass, identiconBackgroundClass]" class="avatar identicon">
{{ identiconTitle }}
</div>
</template>
......@@ -3,7 +3,7 @@ import { GlButton, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import { isString } from 'lodash';
import highlight from '~/lib/utils/highlight';
import { truncateNamespace } from '~/lib/utils/text_utility';
import ProjectAvatar from '~/vue_shared/components/deprecated_project_avatar/default.vue';
import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
export default {
name: 'ProjectListItem',
......@@ -22,6 +22,9 @@ export default {
matcher: { type: String, required: false, default: '' },
},
computed: {
projectAvatarUrl() {
return this.project.avatar_url || this.project.avatarUrl;
},
projectNameWithNamespace() {
return this.project.nameWithNamespace || this.project.name_with_namespace;
},
......@@ -49,7 +52,11 @@ export default {
class="gl-display-flex gl-align-items-center gl-flex-wrap project-namespace-name-container"
>
<gl-icon v-if="selected" class="js-selected-icon" name="mobile-issue-close" />
<project-avatar class="gl-flex-shrink-0 js-project-avatar" :project="project" :size="32" />
<project-avatar
:project-avatar-url="projectAvatarUrl"
:project-name="projectNameWithNamespace"
class="gl-mr-3"
/>
<div
v-if="truncatedNamespace"
:title="projectNameWithNamespace"
......
......@@ -45052,9 +45052,6 @@ msgstr ""
msgid "project access tokens"
msgstr ""
msgid "project avatar"
msgstr ""
msgid "project bots cannot be added to other groups / projects"
msgstr ""
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Identicon entity id is a GraphQL id matches snapshot 1`] = `
<div
class="avatar identicon s40 bg2"
>
E
</div>
`;
exports[`Identicon entity id is a number matches snapshot 1`] = `
<div
class="avatar identicon s40 bg2"
>
E
</div>
`;
import { shallowMount } from '@vue/test-utils';
import IdenticonComponent from '~/vue_shared/components/identicon.vue';
describe('Identicon', () => {
let wrapper;
const defaultProps = {
entityId: 1,
entityName: 'entity-name',
sizeClass: 's40',
};
const createComponent = (props = {}) => {
wrapper = shallowMount(IdenticonComponent, {
propsData: {
...defaultProps,
...props,
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('entity id is a number', () => {
beforeEach(() => createComponent());
it('matches snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('adds a correct class to identicon', () => {
expect(wrapper.find({ ref: 'identicon' }).classes()).toContain('bg2');
});
});
describe('entity id is a GraphQL id', () => {
beforeEach(() => createComponent({ entityId: 'gid://gitlab/Project/8' }));
it('matches snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('adds a correct class to identicon', () => {
expect(wrapper.find({ ref: 'identicon' }).classes()).toContain('bg2');
});
});
});
import Vue, { nextTick } from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import { projectData } from 'jest/ide/mock_data';
import { TEST_HOST } from 'spec/test_constants';
import { getFirstCharacterCapitalized } from '~/lib/utils/text_utility';
import ProjectAvatarDefault from '~/vue_shared/components/deprecated_project_avatar/default.vue';
describe('ProjectAvatarDefault component', () => {
const Component = Vue.extend(ProjectAvatarDefault);
let vm;
beforeEach(() => {
vm = mountComponent(Component, {
project: projectData,
});
});
afterEach(() => {
vm.$destroy();
});
it('renders identicon if project has no avatar_url', async () => {
const expectedText = getFirstCharacterCapitalized(projectData.name);
vm.project = {
...vm.project,
avatar_url: null,
};
await nextTick();
const identiconEl = vm.$el.querySelector('.identicon');
expect(identiconEl).not.toBe(null);
expect(identiconEl.textContent.trim()).toEqual(expectedText);
});
it('renders avatar image if project has avatar_url', async () => {
const avatarUrl = `${TEST_HOST}/images/home/nasa.svg`;
vm.project = {
...vm.project,
avatar_url: avatarUrl,
};
await nextTick();
expect(vm.$el.querySelector('.avatar')).not.toBeNull();
expect(vm.$el.querySelector('.identicon')).toBeNull();
expect(vm.$el.querySelector('img')).toHaveAttr('src', avatarUrl);
});
});
......@@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import mockProjects from 'test_fixtures_static/projects.json';
import { trimText } from 'helpers/text_helper';
import ProjectAvatar from '~/vue_shared/components/deprecated_project_avatar/default.vue';
import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue';
describe('ProjectListItem component', () => {
......@@ -52,8 +52,13 @@ describe('ProjectListItem component', () => {
it(`renders the project avatar`, () => {
wrapper = shallowMount(Component, options);
const avatar = wrapper.findComponent(ProjectAvatar);
expect(wrapper.findComponent(ProjectAvatar).exists()).toBe(true);
expect(avatar.exists()).toBe(true);
expect(avatar.props()).toMatchObject({
projectAvatarUrl: '',
projectName: project.name_with_namespace,
});
});
it(`renders a simple namespace name with a trailing slash`, () => {
......
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