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
......
......@@ -15,17 +15,17 @@
*/
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,8 +19,7 @@ describe('User Avatar Image Component', () => {
wrapper.destroy();
});
describe('`glAvatarForAllUserAvatars` feature flag enabled', () => {
describe('Initialization', () => {
describe('when `glAvatarForAllUserAvatars` feature flag enabled', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage, {
propsData: {
......@@ -41,159 +33,29 @@ describe('User Avatar Image Component', () => {
});
});
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 render `UserAvatarImageNew` component', () => {
expect(wrapper.findComponent(UserAvatarImageNew).exists()).toBe(true);
expect(wrapper.findComponent(UserAvatarImageOld).exists()).toBe(false);
});
});
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', () => {
describe('when `glAvatarForAllUserAvatars` feature flag disabled', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage, {
propsData: {
...PROVIDED_PROPS,
lazy: true,
},
provide: {
glFeatures: {
glAvatarForAllUserAvatars: true,
},
glAvatarForAllUserAvatars: false,
},
});
});
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,
},
});
});
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 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';
describe('User Avatar Link Component', () => {
let wrapper;
const defaultProps = {
linkHref: `${TEST_HOST}/myavatarurl.com`,
imgSize: 99,
imgSrc: `${TEST_HOST}/myavatarurl.com`,
const PROVIDED_PROPS = {
size: 32,
imgSrc: 'myavatarurl.com',
imgAlt: 'mydisplayname',
imgCssClasses: 'myextraavatarclass',
cssClasses: 'myextraavatarclass',
tooltipText: 'tooltip text',
tooltipPlacement: 'bottom',
username: 'username',
};
const createWrapper = (props) => {
wrapper = shallowMount(UserAvatarLink, {
propsData: {
...defaultProps,
...props,
},
});
};
};
beforeEach(() => {
createWrapper();
});
describe('User Avatar Link Component', () => {
let wrapper;
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: '',
});
});
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);
});
wrapper = shallowMount(UserAvatarLink, {
propsData: {
...PROVIDED_PROPS,
},
provide: {
glFeatures: {
glAvatarForAllUserAvatars: true,
},
},
});
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 `UserAvatarLinkNew` component', () => {
expect(wrapper.findComponent(UserAvatarLinkNew).exists()).toBe(true);
expect(wrapper.findComponent(UserAvatarLinkOld).exists()).toBe(false);
});
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('when `glAvatarForAllUserAvatars` feature flag disabled', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarLink, {
propsData: {
...PROVIDED_PROPS,
},
provide: {
glFeatures: {
glAvatarForAllUserAvatars: false,
},
},
});
describe('lazy', () => {
it('passes lazy prop to avatar image', () => {
createWrapper({
username: '',
lazy: true,
});
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