Commit 09e24f82 authored by Peter Hegman's avatar Peter Hegman

Merge branch '335798-use-gl-avatar-link-to-reneder-linked-avatars' into 'master'

Render user avatar link using `GlAvatar`

See merge request gitlab-org/gitlab!82736
parents 51487148 85e259f0
......@@ -163,8 +163,8 @@ export default {
v-if="diffFile.discussions.length"
class="diff-file-discussions"
:discussions="diffFile.discussions"
:should-collapse-discussions="true"
:render-avatar-badge="true"
should-collapse-discussions
render-avatar-badge
/>
<diff-file-drafts :file-hash="diffFileHash" class="diff-file-discussions" />
<note-form
......
<script>
/* This is a re-usable vue component for rendering a user avatar that
does not need to link to the user's profile. The image and an optional
tooltip can be configured by props passed to this component.
does not need to link to the user's profile. The image and an optional
tooltip can be configured by props passed to this component.
Sample configuration:
Sample configuration:
<user-avatar-image
lazy
:img-src="userAvatarSrc"
:img-alt="tooltipText"
:tooltip-text="tooltipText"
tooltip-placement="top"
/>
<user-avatar-image
lazy
:img-src="userAvatarSrc"
:img-alt="tooltipText"
:tooltip-text="tooltipText"
tooltip-placement="top"
/>
*/
*/
import { GlTooltip, GlAvatar } from '@gitlab/ui';
import defaultAvatarUrl from 'images/no_avatar.png';
import { __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { placeholderImage } from '../../../lazy_loader';
import UserAvatarImageNew from './user_avatar_image_new.vue';
import UserAvatarImageOld from './user_avatar_image_old.vue';
export default {
name: 'UserAvatarImage',
components: {
GlTooltip,
GlAvatar,
UserAvatarImageNew,
UserAvatarImageOld,
},
mixins: [glFeatureFlagMixin()],
props: {
......@@ -65,62 +65,14 @@ export default {
default: 'top',
},
},
computed: {
// API response sends null when gravatar is disabled and
// we provide an empty string when we use it inside user avatar link.
// In both cases we should render the defaultAvatarUrl
sanitizedSource() {
let baseSrc = this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc;
// Only adds the width to the URL if its not a base64 data image
if (!(baseSrc.indexOf('data:') === 0) && !baseSrc.includes('?'))
baseSrc += `?width=${this.size}`;
return baseSrc;
},
resultantSrcAttribute() {
return this.lazy ? placeholderImage : this.sanitizedSource;
},
avatarSizeClass() {
return `s${this.size}`;
},
},
};
</script>
<template>
<span>
<gl-avatar
v-if="glFeatures.glAvatarForAllUserAvatars"
ref="userAvatarImage"
:class="{
lazy: lazy,
[cssClasses]: true,
}"
:src="resultantSrcAttribute"
:data-src="sanitizedSource"
:size="size"
:alt="imgAlt"
/>
<img
v-else
ref="userAvatarImage"
:class="{
lazy: lazy,
[avatarSizeClass]: true,
[cssClasses]: true,
}"
:src="resultantSrcAttribute"
:width="size"
:height="size"
:alt="imgAlt"
:data-src="sanitizedSource"
class="avatar"
/>
<gl-tooltip
:target="() => $refs.userAvatarImage"
:placement="tooltipPlacement"
boundary="window"
>
<slot> {{ tooltipText }} </slot>
</gl-tooltip>
</span>
<user-avatar-image-new v-if="glFeatures.glAvatarForAllUserAvatars" v-bind="$props">
<slot></slot>
</user-avatar-image-new>
<user-avatar-image-old v-else v-bind="$props">
<slot></slot>
</user-avatar-image-old>
</template>
<script>
/* This is a re-usable vue component for rendering a user avatar that
does not need to link to the user's profile. The image and an optional
tooltip can be configured by props passed to this component.
Sample configuration:
<user-avatar
lazy
:img-src="userAvatarSrc"
:img-alt="tooltipText"
:tooltip-text="tooltipText"
tooltip-placement="top"
/>
*/
import { GlTooltip, GlAvatar } from '@gitlab/ui';
import defaultAvatarUrl from 'images/no_avatar.png';
import { __ } from '~/locale';
import { placeholderImage } from '../../../lazy_loader';
export default {
name: 'UserAvatarImageNew',
components: {
GlTooltip,
GlAvatar,
},
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: __('user avatar'),
},
size: {
type: Number,
required: false,
default: 20,
},
tooltipText: {
type: String,
required: false,
default: '',
},
tooltipPlacement: {
type: String,
required: false,
default: 'top',
},
},
computed: {
// API response sends null when gravatar is disabled and
// we provide an empty string when we use it inside user avatar link.
// In both cases we should render the defaultAvatarUrl
sanitizedSource() {
let baseSrc = this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc;
// Only adds the width to the URL if its not a base64 data image
if (!(baseSrc.indexOf('data:') === 0) && !baseSrc.includes('?'))
baseSrc += `?width=${this.size}`;
return baseSrc;
},
resultantSrcAttribute() {
return this.lazy ? placeholderImage : this.sanitizedSource;
},
},
};
</script>
<template>
<span>
<gl-avatar
ref="userAvatar"
:class="{
lazy: lazy,
[cssClasses]: true,
}"
:src="resultantSrcAttribute"
:data-src="sanitizedSource"
:size="size"
:alt="imgAlt"
/>
<gl-tooltip
:target="() => $refs.userAvatar.$el"
:placement="tooltipPlacement"
boundary="window"
>
<slot> {{ tooltipText }}</slot>
</gl-tooltip>
</span>
</template>
<script>
/* This is a re-usable vue component for rendering a user avatar that
does not need to link to the user's profile. The image and an optional
tooltip can be configured by props passed to this component.
Sample configuration:
<user-avatar-image
lazy
:img-src="userAvatarSrc"
:img-alt="tooltipText"
:tooltip-text="tooltipText"
tooltip-placement="top"
/>
*/
import { GlTooltip } from '@gitlab/ui';
import defaultAvatarUrl from 'images/no_avatar.png';
import { __ } from '~/locale';
import { placeholderImage } from '../../../lazy_loader';
export default {
name: 'UserAvatarImageOld',
components: {
GlTooltip,
},
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: __('user avatar'),
},
size: {
type: Number,
required: false,
default: 20,
},
tooltipText: {
type: String,
required: false,
default: '',
},
tooltipPlacement: {
type: String,
required: false,
default: 'top',
},
},
computed: {
// API response sends null when gravatar is disabled and
// we provide an empty string when we use it inside user avatar link.
// In both cases we should render the defaultAvatarUrl
sanitizedSource() {
let baseSrc = this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc;
// Only adds the width to the URL if its not a base64 data image
if (!(baseSrc.indexOf('data:') === 0) && !baseSrc.includes('?'))
baseSrc += `?width=${this.size}`;
return baseSrc;
},
resultantSrcAttribute() {
return this.lazy ? placeholderImage : this.sanitizedSource;
},
avatarSizeClass() {
return `s${this.size}`;
},
},
};
</script>
<template>
<span>
<img
ref="userAvatarImage"
:class="{
lazy: lazy,
[avatarSizeClass]: true,
[cssClasses]: true,
}"
:src="resultantSrcAttribute"
:width="size"
:height="size"
:alt="imgAlt"
:data-src="sanitizedSource"
class="avatar"
/>
<gl-tooltip
:target="() => $refs.userAvatarImage"
:placement="tooltipPlacement"
boundary="window"
>
<slot> {{ tooltipText }}</slot>
</gl-tooltip>
</span>
</template>
......@@ -17,18 +17,17 @@
*/
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import userAvatarImage from './user_avatar_image.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import UserAvatarLinkNew from './user_avatar_link_new.vue';
import UserAvatarLinkOld from './user_avatar_link_old.vue';
export default {
name: 'UserAvatarLink',
components: {
GlLink,
userAvatarImage,
},
directives: {
GlTooltip: GlTooltipDirective,
UserAvatarLinkNew,
UserAvatarLinkOld,
},
mixins: [glFeatureFlagMixin()],
props: {
lazy: {
type: Boolean,
......@@ -76,36 +75,21 @@ export default {
default: '',
},
},
computed: {
shouldShowUsername() {
return this.username.length > 0;
},
avatarTooltipText() {
return this.shouldShowUsername ? '' : this.tooltipText;
},
},
};
</script>
<template>
<gl-link :href="linkHref" class="user-avatar-link">
<user-avatar-image
:img-src="imgSrc"
:img-alt="imgAlt"
:css-classes="imgCssClasses"
:size="imgSize"
:tooltip-text="avatarTooltipText"
:tooltip-placement="tooltipPlacement"
:lazy="lazy"
>
<slot></slot> </user-avatar-image
><span
v-if="shouldShowUsername"
v-gl-tooltip
:title="tooltipText"
:tooltip-placement="tooltipPlacement"
class="js-user-avatar-link-username"
>{{ username }}</span
><slot name="avatar-badge"></slot>
</gl-link>
<user-avatar-link-new v-if="glFeatures.glAvatarForAllUserAvatars" v-bind="$props">
<slot></slot>
<template #avatar-badge>
<slot name="avatar-badge"></slot>
</template>
</user-avatar-link-new>
<user-avatar-link-old v-else v-bind="$props">
<slot></slot>
<template #avatar-badge>
<slot name="avatar-badge"></slot>
</template>
</user-avatar-link-old>
</template>
<script>
/* This is a re-usable vue component for rendering a user avatar wrapped in
a clickable link (likely to the user's profile). The link, image, and
tooltip can be configured by props passed to this component.
Sample configuration:
<user-avatar-link
:link-href="userProfileUrl"
:img-src="userAvatarSrc"
:img-alt="tooltipText"
:img-size="20"
:tooltip-text="tooltipText"
:tooltip-placement="top"
:username="username"
/>
*/
import { GlAvatarLink, GlTooltipDirective } from '@gitlab/ui';
import UserAvatarImage from './user_avatar_image.vue';
export default {
name: 'UserAvatarLinkNew',
components: {
UserAvatarImage,
GlAvatarLink,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
lazy: {
type: Boolean,
required: false,
default: false,
},
linkHref: {
type: String,
required: false,
default: '',
},
imgSrc: {
type: String,
required: false,
default: '',
},
imgAlt: {
type: String,
required: false,
default: '',
},
imgCssClasses: {
type: String,
required: false,
default: '',
},
imgSize: {
type: Number,
required: false,
default: 20,
},
tooltipText: {
type: String,
required: false,
default: '',
},
tooltipPlacement: {
type: String,
required: false,
default: 'top',
},
username: {
type: String,
required: false,
default: '',
},
},
computed: {
shouldShowUsername() {
return this.username.length > 0;
},
avatarTooltipText() {
return this.shouldShowUsername ? '' : this.tooltipText;
},
},
};
</script>
<template>
<gl-avatar-link :href="linkHref" class="user-avatar-link">
<user-avatar-image
:img-src="imgSrc"
:img-alt="imgAlt"
:css-classes="imgCssClasses"
:size="imgSize"
:tooltip-text="avatarTooltipText"
:tooltip-placement="tooltipPlacement"
:lazy="lazy"
>
<slot></slot>
</user-avatar-image>
<span
v-if="shouldShowUsername"
v-gl-tooltip
:title="tooltipText"
:tooltip-placement="tooltipPlacement"
class="gl-ml-3"
data-testid="user-avatar-link-username"
>
{{ username }}
</span>
<slot name="avatar-badge"></slot>
</gl-avatar-link>
</template>
<script>
/* This is a re-usable vue component for rendering a user avatar wrapped in
a clickable link (likely to the user's profile). The link, image, and
tooltip can be configured by props passed to this component.
Sample configuration:
<user-avatar-link
:link-href="userProfileUrl"
:img-src="userAvatarSrc"
:img-alt="tooltipText"
:img-size="20"
:tooltip-text="tooltipText"
:tooltip-placement="top"
:username="username"
/>
*/
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import UserAvatarImage from './user_avatar_image.vue';
export default {
name: 'UserAvatarLinkOld',
components: {
GlLink,
UserAvatarImage,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
lazy: {
type: Boolean,
required: false,
default: false,
},
linkHref: {
type: String,
required: false,
default: '',
},
imgSrc: {
type: String,
required: false,
default: '',
},
imgAlt: {
type: String,
required: false,
default: '',
},
imgCssClasses: {
type: String,
required: false,
default: '',
},
imgSize: {
type: Number,
required: false,
default: 20,
},
tooltipText: {
type: String,
required: false,
default: '',
},
tooltipPlacement: {
type: String,
required: false,
default: 'top',
},
username: {
type: String,
required: false,
default: '',
},
},
computed: {
shouldShowUsername() {
return this.username.length > 0;
},
avatarTooltipText() {
return this.shouldShowUsername ? '' : this.tooltipText;
},
},
};
</script>
<template>
<span>
<gl-link :href="linkHref" class="user-avatar-link">
<user-avatar-image
:img-src="imgSrc"
:img-alt="imgAlt"
:css-classes="imgCssClasses"
:size="imgSize"
:tooltip-text="avatarTooltipText"
:tooltip-placement="tooltipPlacement"
:lazy="lazy"
>
<slot></slot>
</user-avatar-image>
<span
v-if="shouldShowUsername"
v-gl-tooltip
:title="tooltipText"
:tooltip-placement="tooltipPlacement"
data-testid="user-avatar-link-username"
>
{{ username }}
</span>
<slot name="avatar-badge"></slot>
</gl-link>
</span>
</template>
......@@ -170,7 +170,7 @@ RSpec.describe 'Epic show', :js do
page.within('.epic-page-container .detail-page-header-body') do
expect(find('.issuable-status-box > span')).to have_content('Open')
expect(find('.issuable-meta')).to have_content('Created')
expect(find('.issuable-meta .js-user-avatar-link-username')).to have_content('Rick Sanchez')
expect(find('.issuable-meta [data-testid="user-avatar-link-username"]')).to have_content('Rick Sanchez')
end
end
......
......@@ -79,7 +79,7 @@ describe('Environment item', () => {
describe('With user information', () => {
it('should render user avatar with link to profile', () => {
expect(wrapper.find('.js-deploy-user-container').attributes('href')).toEqual(
expect(wrapper.find('.js-deploy-user-container').props('linkHref')).toEqual(
environment.last_deployment.user.web_url,
);
});
......
import { shallowMount } from '@vue/test-utils';
import { GlAvatar, GlTooltip } from '@gitlab/ui';
import defaultAvatarUrl from 'images/no_avatar.png';
import { placeholderImage } from '~/lazy_loader';
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image_new.vue';
jest.mock('images/no_avatar.png', () => 'default-avatar-url');
const PROVIDED_PROPS = {
size: 32,
imgSrc: 'myavatarurl.com',
imgAlt: 'mydisplayname',
cssClasses: 'myextraavatarclass',
tooltipText: 'tooltip text',
tooltipPlacement: 'bottom',
};
describe('User Avatar Image Component', () => {
let wrapper;
afterEach(() => {
wrapper.destroy();
});
describe('Initialization', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage, {
propsData: {
...PROVIDED_PROPS,
},
provide: {
glFeatures: {
glAvatarForAllUserAvatars: true,
},
},
});
});
it('should render `GlAvatar` and provide correct properties to it', () => {
const avatar = wrapper.findComponent(GlAvatar);
expect(avatar.attributes('data-src')).toBe(
`${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
);
expect(avatar.props()).toMatchObject({
src: `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
alt: PROVIDED_PROPS.imgAlt,
size: PROVIDED_PROPS.size,
});
});
it('should add correct CSS classes', () => {
const classes = wrapper.findComponent(GlAvatar).classes();
expect(classes).toContain(PROVIDED_PROPS.cssClasses);
expect(classes).not.toContain('lazy');
});
});
describe('Initialization when lazy', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage, {
propsData: {
...PROVIDED_PROPS,
lazy: true,
},
provide: {
glFeatures: {
glAvatarForAllUserAvatars: true,
},
},
});
});
it('should add lazy attributes', () => {
const avatar = wrapper.findComponent(GlAvatar);
expect(avatar.classes()).toContain('lazy');
expect(avatar.attributes()).toMatchObject({
src: placeholderImage,
'data-src': `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
});
});
});
describe('Initialization without src', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage, {
propsData: {
...PROVIDED_PROPS,
imgSrc: null,
},
provide: {
glFeatures: {
glAvatarForAllUserAvatars: true,
},
},
});
});
it('should have default avatar image', () => {
const avatar = wrapper.findComponent(GlAvatar);
expect(avatar.props('src')).toBe(`${defaultAvatarUrl}?width=${PROVIDED_PROPS.size}`);
});
});
describe('Dynamic tooltip content', () => {
const slots = {
default: ['Action!'],
};
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage, {
propsData: PROVIDED_PROPS,
slots,
});
});
it('renders the tooltip slot', () => {
expect(wrapper.findComponent(GlTooltip).exists()).toBe(true);
});
it('renders the tooltip content', () => {
expect(wrapper.findComponent(GlTooltip).text()).toContain(slots.default[0]);
});
});
});
import { shallowMount } from '@vue/test-utils';
import { GlTooltip } from '@gitlab/ui';
import defaultAvatarUrl from 'images/no_avatar.png';
import { placeholderImage } from '~/lazy_loader';
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image_old.vue';
jest.mock('images/no_avatar.png', () => 'default-avatar-url');
const PROVIDED_PROPS = {
size: 32,
imgSrc: 'myavatarurl.com',
imgAlt: 'mydisplayname',
cssClasses: 'myextraavatarclass',
tooltipText: 'tooltip text',
tooltipPlacement: 'bottom',
};
const DEFAULT_PROPS = {
size: 20,
};
describe('User Avatar Image Component', () => {
let wrapper;
afterEach(() => {
wrapper.destroy();
});
describe('Initialization', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage, {
propsData: {
...PROVIDED_PROPS,
},
});
});
it('should have <img> as a child element', () => {
const imageElement = wrapper.find('img');
expect(imageElement.exists()).toBe(true);
expect(imageElement.attributes('src')).toBe(
`${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
);
expect(imageElement.attributes('data-src')).toBe(
`${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
);
expect(imageElement.attributes('alt')).toBe(PROVIDED_PROPS.imgAlt);
});
it('should properly render img css', () => {
const classes = wrapper.find('img').classes();
expect(classes).toEqual(['avatar', 's32', PROVIDED_PROPS.cssClasses]);
expect(classes).not.toContain('lazy');
});
});
describe('Initialization when lazy', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage, {
propsData: {
...PROVIDED_PROPS,
lazy: true,
},
});
});
it('should add lazy attributes', () => {
const imageElement = wrapper.find('img');
expect(imageElement.classes()).toContain('lazy');
expect(imageElement.attributes('src')).toBe(placeholderImage);
expect(imageElement.attributes('data-src')).toBe(
`${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
);
});
});
describe('Initialization without src', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage);
});
it('should have default avatar image', () => {
const imageElement = wrapper.find('img');
expect(imageElement.attributes('src')).toBe(
`${defaultAvatarUrl}?width=${DEFAULT_PROPS.size}`,
);
});
});
describe('dynamic tooltip content', () => {
const props = PROVIDED_PROPS;
const slots = {
default: ['Action!'],
};
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage, {
propsData: { props },
slots,
});
});
it('renders the tooltip slot', () => {
expect(wrapper.findComponent(GlTooltip).exists()).toBe(true);
});
it('renders the tooltip content', () => {
expect(wrapper.findComponent(GlTooltip).text()).toContain(slots.default[0]);
});
it('does not render tooltip data attributes on avatar image', () => {
const avatarImg = wrapper.find('img');
expect(avatarImg.attributes('title')).toBeFalsy();
expect(avatarImg.attributes('data-placement')).not.toBeDefined();
expect(avatarImg.attributes('data-container')).not.toBeDefined();
});
});
});
import { shallowMount } from '@vue/test-utils';
import { GlAvatar, GlTooltip } from '@gitlab/ui';
import defaultAvatarUrl from 'images/no_avatar.png';
import { placeholderImage } from '~/lazy_loader';
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
jest.mock('images/no_avatar.png', () => 'default-avatar-url');
import UserAvatarImageNew from '~/vue_shared/components/user_avatar/user_avatar_image_new.vue';
import UserAvatarImageOld from '~/vue_shared/components/user_avatar/user_avatar_image_old.vue';
const PROVIDED_PROPS = {
size: 32,
......@@ -15,10 +12,6 @@ const PROVIDED_PROPS = {
tooltipPlacement: 'bottom',
};
const DEFAULT_PROPS = {
size: 20,
};
describe('User Avatar Image Component', () => {
let wrapper;
......@@ -26,174 +19,43 @@ describe('User Avatar Image Component', () => {
wrapper.destroy();
});
describe('`glAvatarForAllUserAvatars` feature flag enabled', () => {
describe('Initialization', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage, {
propsData: {
...PROVIDED_PROPS,
},
provide: {
glFeatures: {
glAvatarForAllUserAvatars: true,
},
},
});
});
it('should render `GlAvatar` and provide correct properties to it', () => {
const avatar = wrapper.findComponent(GlAvatar);
expect(avatar.attributes('data-src')).toBe(
`${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
);
expect(avatar.props()).toMatchObject({
src: `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
alt: PROVIDED_PROPS.imgAlt,
});
});
it('should add correct CSS classes', () => {
const classes = wrapper.findComponent(GlAvatar).classes();
expect(classes).toContain(PROVIDED_PROPS.cssClasses);
expect(classes).not.toContain('lazy');
});
});
describe('Initialization when lazy', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage, {
propsData: {
...PROVIDED_PROPS,
lazy: true,
},
provide: {
glFeatures: {
glAvatarForAllUserAvatars: true,
},
},
});
});
it('should add lazy attributes', () => {
const avatar = wrapper.findComponent(GlAvatar);
expect(avatar.classes()).toContain('lazy');
expect(avatar.attributes()).toMatchObject({
src: placeholderImage,
'data-src': `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
});
});
});
describe('Initialization without src', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage);
});
it('should have default avatar image', () => {
const imageElement = wrapper.find('img');
expect(imageElement.attributes('src')).toBe(
`${defaultAvatarUrl}?width=${DEFAULT_PROPS.size}`,
);
});
});
});
describe('`glAvatarForAllUserAvatars` feature flag disabled', () => {
describe('Initialization', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage, {
propsData: {
...PROVIDED_PROPS,
},
});
});
it('should have <img> as a child element', () => {
const imageElement = wrapper.find('img');
expect(imageElement.exists()).toBe(true);
expect(imageElement.attributes('src')).toBe(
`${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
);
expect(imageElement.attributes('data-src')).toBe(
`${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
);
expect(imageElement.attributes('alt')).toBe(PROVIDED_PROPS.imgAlt);
});
it('should properly render img css', () => {
const classes = wrapper.find('img').classes();
expect(classes).toEqual(['avatar', 's32', PROVIDED_PROPS.cssClasses]);
expect(classes).not.toContain('lazy');
});
});
describe('Initialization when lazy', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage, {
propsData: {
...PROVIDED_PROPS,
lazy: true,
describe('when `glAvatarForAllUserAvatars` feature flag enabled', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage, {
propsData: {
...PROVIDED_PROPS,
},
provide: {
glFeatures: {
glAvatarForAllUserAvatars: true,
},
});
});
it('should add lazy attributes', () => {
const imageElement = wrapper.find('img');
expect(imageElement.classes()).toContain('lazy');
expect(imageElement.attributes('src')).toBe(placeholderImage);
expect(imageElement.attributes('data-src')).toBe(
`${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
);
},
});
});
describe('Initialization without src', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage);
});
it('should have default avatar image', () => {
const imageElement = wrapper.find('img');
expect(imageElement.attributes('src')).toBe(
`${defaultAvatarUrl}?width=${DEFAULT_PROPS.size}`,
);
});
it('should render `UserAvatarImageNew` component', () => {
expect(wrapper.findComponent(UserAvatarImageNew).exists()).toBe(true);
expect(wrapper.findComponent(UserAvatarImageOld).exists()).toBe(false);
});
});
describe('dynamic tooltip content', () => {
const props = PROVIDED_PROPS;
const slots = {
default: ['Action!'],
};
describe('when `glAvatarForAllUserAvatars` feature flag disabled', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage, {
propsData: { props },
slots,
propsData: {
...PROVIDED_PROPS,
},
provide: {
glFeatures: {
glAvatarForAllUserAvatars: false,
},
},
});
});
it('renders the tooltip slot', () => {
expect(wrapper.findComponent(GlTooltip).exists()).toBe(true);
});
it('renders the tooltip content', () => {
expect(wrapper.findComponent(GlTooltip).text()).toContain(slots.default[0]);
});
it('does not render tooltip data attributes for on avatar image', () => {
const avatarImg = wrapper.find('img');
expect(avatarImg.attributes('title')).toBeFalsy();
expect(avatarImg.attributes('data-placement')).not.toBeDefined();
expect(avatarImg.attributes('data-container')).not.toBeDefined();
it('should render `UserAvatarImageOld` component', () => {
expect(wrapper.findComponent(UserAvatarImageNew).exists()).toBe(false);
expect(wrapper.findComponent(UserAvatarImageOld).exists()).toBe(true);
});
});
});
import { GlAvatarLink } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { TEST_HOST } from 'spec/test_constants';
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link_new.vue';
describe('User Avatar Link Component', () => {
let wrapper;
const findUserName = () => wrapper.findByTestId('user-avatar-link-username');
const defaultProps = {
linkHref: `${TEST_HOST}/myavatarurl.com`,
imgSize: 32,
imgSrc: `${TEST_HOST}/myavatarurl.com`,
imgAlt: 'mydisplayname',
imgCssClasses: 'myextraavatarclass',
tooltipText: 'tooltip text',
tooltipPlacement: 'bottom',
username: 'username',
};
const createWrapper = (props, slots) => {
wrapper = shallowMountExtended(UserAvatarLink, {
propsData: {
...defaultProps,
...props,
...slots,
},
});
};
beforeEach(() => {
createWrapper();
});
afterEach(() => {
wrapper.destroy();
});
it('should render GlLink with correct props', () => {
const link = wrapper.findComponent(GlAvatarLink);
expect(link.exists()).toBe(true);
expect(link.attributes('href')).toBe(defaultProps.linkHref);
});
it('should render UserAvatarImage and provide correct props to it', () => {
expect(wrapper.findComponent(UserAvatarImage).exists()).toBe(true);
expect(wrapper.findComponent(UserAvatarImage).props()).toEqual({
cssClasses: defaultProps.imgCssClasses,
imgAlt: defaultProps.imgAlt,
imgSrc: defaultProps.imgSrc,
lazy: false,
size: defaultProps.imgSize,
tooltipPlacement: defaultProps.tooltipPlacement,
tooltipText: '',
});
});
describe('when username provided', () => {
beforeEach(() => {
createWrapper({ username: defaultProps.username });
});
it('should render provided username', () => {
expect(findUserName().text()).toBe(defaultProps.username);
});
it('should provide the tooltip data for the username', () => {
expect(findUserName().attributes()).toEqual(
expect.objectContaining({
title: defaultProps.tooltipText,
'tooltip-placement': defaultProps.tooltipPlacement,
}),
);
});
});
describe('when username is NOT provided', () => {
beforeEach(() => {
createWrapper({ username: '' });
});
it('should NOT render username', () => {
expect(findUserName().exists()).toBe(false);
});
});
describe('avatar-badge slot', () => {
const badge = '<span>User badge</span>';
beforeEach(() => {
createWrapper(defaultProps, {
'avatar-badge': badge,
});
});
it('should render provided `avatar-badge` slot content', () => {
expect(wrapper.html()).toContain(badge);
});
});
});
import { GlLink } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { TEST_HOST } from 'spec/test_constants';
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link_old.vue';
describe('User Avatar Link Component', () => {
let wrapper;
const findUserName = () => wrapper.find('[data-testid="user-avatar-link-username"]');
const defaultProps = {
linkHref: `${TEST_HOST}/myavatarurl.com`,
imgSize: 32,
imgSrc: `${TEST_HOST}/myavatarurl.com`,
imgAlt: 'mydisplayname',
imgCssClasses: 'myextraavatarclass',
tooltipText: 'tooltip text',
tooltipPlacement: 'bottom',
username: 'username',
};
const createWrapper = (props, slots) => {
wrapper = shallowMountExtended(UserAvatarLink, {
propsData: {
...defaultProps,
...props,
...slots,
},
});
};
beforeEach(() => {
createWrapper();
});
afterEach(() => {
wrapper.destroy();
});
it('should render GlLink with correct props', () => {
const link = wrapper.findComponent(GlLink);
expect(link.exists()).toBe(true);
expect(link.attributes('href')).toBe(defaultProps.linkHref);
});
it('should render UserAvatarImage and povide correct props to it', () => {
expect(wrapper.findComponent(UserAvatarImage).exists()).toBe(true);
expect(wrapper.findComponent(UserAvatarImage).props()).toEqual({
cssClasses: defaultProps.imgCssClasses,
imgAlt: defaultProps.imgAlt,
imgSrc: defaultProps.imgSrc,
lazy: false,
size: defaultProps.imgSize,
tooltipPlacement: defaultProps.tooltipPlacement,
tooltipText: '',
});
});
describe('when username provided', () => {
beforeEach(() => {
createWrapper({ username: defaultProps.username });
});
it('should render provided username', () => {
expect(findUserName().text()).toBe(defaultProps.username);
});
it('should provide the tooltip data for the username', () => {
expect(findUserName().attributes()).toEqual(
expect.objectContaining({
title: defaultProps.tooltipText,
'tooltip-placement': defaultProps.tooltipPlacement,
}),
);
});
});
describe('when username is NOT provided', () => {
beforeEach(() => {
createWrapper({ username: '' });
});
it('should NOT render username', () => {
expect(findUserName().exists()).toBe(false);
});
});
describe('avatar-badge slot', () => {
const badge = '<span>User badge</span>';
beforeEach(() => {
createWrapper(defaultProps, {
'avatar-badge': badge,
});
});
it('should render provided `avatar-badge` slot content', () => {
expect(wrapper.html()).toContain(badge);
});
});
});
import { GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { each } from 'lodash';
import { trimText } from 'helpers/text_helper';
import { TEST_HOST } from 'spec/test_constants';
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import UserAvatarLinkNew from '~/vue_shared/components/user_avatar/user_avatar_link_new.vue';
import UserAvatarLinkOld from '~/vue_shared/components/user_avatar/user_avatar_link_old.vue';
const PROVIDED_PROPS = {
size: 32,
imgSrc: 'myavatarurl.com',
imgAlt: 'mydisplayname',
cssClasses: 'myextraavatarclass',
tooltipText: 'tooltip text',
tooltipPlacement: 'bottom',
};
describe('User Avatar Link Component', () => {
let wrapper;
const defaultProps = {
linkHref: `${TEST_HOST}/myavatarurl.com`,
imgSize: 99,
imgSrc: `${TEST_HOST}/myavatarurl.com`,
imgAlt: 'mydisplayname',
imgCssClasses: 'myextraavatarclass',
tooltipText: 'tooltip text',
tooltipPlacement: 'bottom',
username: 'username',
};
const createWrapper = (props) => {
wrapper = shallowMount(UserAvatarLink, {
propsData: {
...defaultProps,
...props,
},
});
};
beforeEach(() => {
createWrapper();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('should have user-avatar-image registered as child component', () => {
expect(wrapper.vm.$options.components.userAvatarImage).toBeDefined();
});
it('user-avatar-link should have user-avatar-image as child component', () => {
expect(wrapper.find(UserAvatarImage).exists()).toBe(true);
});
it('should render GlLink as a child element', () => {
const link = wrapper.find(GlLink);
expect(link.exists()).toBe(true);
expect(link.attributes('href')).toBe(defaultProps.linkHref);
});
it('should return necessary props as defined', () => {
each(defaultProps, (val, key) => {
expect(wrapper.vm[key]).toBeDefined();
});
});
describe('no username', () => {
describe('when `glAvatarForAllUserAvatars` feature flag enabled', () => {
beforeEach(() => {
createWrapper({
username: '',
wrapper = shallowMount(UserAvatarLink, {
propsData: {
...PROVIDED_PROPS,
},
provide: {
glFeatures: {
glAvatarForAllUserAvatars: true,
},
},
});
});
it('should only render image tag in link', () => {
const childElements = wrapper.vm.$el.childNodes;
expect(wrapper.find('img')).not.toBe('null');
// Vue will render the hidden component as <!---->
expect(childElements[1].tagName).toBeUndefined();
});
it('should render avatar image tooltip', () => {
expect(wrapper.vm.shouldShowUsername).toBe(false);
expect(wrapper.vm.avatarTooltipText).toEqual(defaultProps.tooltipText);
it('should render `UserAvatarLinkNew` component', () => {
expect(wrapper.findComponent(UserAvatarLinkNew).exists()).toBe(true);
expect(wrapper.findComponent(UserAvatarLinkOld).exists()).toBe(false);
});
});
describe('username', () => {
it('should not render avatar image tooltip', () => {
expect(wrapper.find('.js-user-avatar-image-tooltip').exists()).toBe(false);
});
it('should render username prop in <span>', () => {
expect(trimText(wrapper.find('.js-user-avatar-link-username').text())).toEqual(
defaultProps.username,
);
});
it('should render text tooltip for <span>', () => {
expect(wrapper.find('.js-user-avatar-link-username').attributes('title')).toEqual(
defaultProps.tooltipText,
);
});
it('should render text tooltip placement for <span>', () => {
expect(wrapper.find('.js-user-avatar-link-username').attributes('tooltip-placement')).toBe(
defaultProps.tooltipPlacement,
);
});
});
describe('lazy', () => {
it('passes lazy prop to avatar image', () => {
createWrapper({
username: '',
lazy: true,
describe('when `glAvatarForAllUserAvatars` feature flag disabled', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarLink, {
propsData: {
...PROVIDED_PROPS,
},
provide: {
glFeatures: {
glAvatarForAllUserAvatars: false,
},
},
});
});
expect(wrapper.find(UserAvatarImage).props('lazy')).toBe(true);
it('should render `UserAvatarLinkOld` component', () => {
expect(wrapper.findComponent(UserAvatarLinkNew).exists()).toBe(false);
expect(wrapper.findComponent(UserAvatarLinkOld).exists()).toBe(true);
});
});
});
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