Commit 85e259f0 authored by Olena Horal-Koretska's avatar Olena Horal-Koretska Committed by Peter Hegman

Split avatar_image and avatar_link into old and new versions

The new version is behind the feature flag `gl_avatar_for_all_user_avatars`
parent 4c543ced
...@@ -163,8 +163,8 @@ export default { ...@@ -163,8 +163,8 @@ export default {
v-if="diffFile.discussions.length" v-if="diffFile.discussions.length"
class="diff-file-discussions" class="diff-file-discussions"
:discussions="diffFile.discussions" :discussions="diffFile.discussions"
:should-collapse-discussions="true" should-collapse-discussions
:render-avatar-badge="true" render-avatar-badge
/> />
<diff-file-drafts :file-hash="diffFileHash" class="diff-file-discussions" /> <diff-file-drafts :file-hash="diffFileHash" class="diff-file-discussions" />
<note-form <note-form
......
<script> <script>
/* This is a re-usable vue component for rendering a user avatar that /* 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 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. tooltip can be configured by props passed to this component.
Sample configuration: Sample configuration:
<user-avatar-image <user-avatar-image
lazy lazy
:img-src="userAvatarSrc" :img-src="userAvatarSrc"
:img-alt="tooltipText" :img-alt="tooltipText"
:tooltip-text="tooltipText" :tooltip-text="tooltipText"
tooltip-placement="top" tooltip-placement="top"
/> />
*/ */
import { GlTooltip, GlAvatar } from '@gitlab/ui';
import defaultAvatarUrl from 'images/no_avatar.png'; import defaultAvatarUrl from 'images/no_avatar.png';
import { __ } from '~/locale'; import { __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; 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 { export default {
name: 'UserAvatarImage', name: 'UserAvatarImage',
components: { components: {
GlTooltip, UserAvatarImageNew,
GlAvatar, UserAvatarImageOld,
}, },
mixins: [glFeatureFlagMixin()], mixins: [glFeatureFlagMixin()],
props: { props: {
...@@ -65,62 +65,14 @@ export default { ...@@ -65,62 +65,14 @@ export default {
default: 'top', 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> </script>
<template> <template>
<span> <user-avatar-image-new v-if="glFeatures.glAvatarForAllUserAvatars" v-bind="$props">
<gl-avatar <slot></slot>
v-if="glFeatures.glAvatarForAllUserAvatars" </user-avatar-image-new>
ref="userAvatarImage" <user-avatar-image-old v-else v-bind="$props">
:class="{ <slot></slot>
lazy: lazy, </user-avatar-image-old>
[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>
</template> </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 @@ ...@@ -17,18 +17,17 @@
*/ */
import { GlLink, GlTooltipDirective } from '@gitlab/ui'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import userAvatarImage from './user_avatar_image.vue'; import UserAvatarLinkNew from './user_avatar_link_new.vue';
import UserAvatarLinkOld from './user_avatar_link_old.vue';
export default { export default {
name: 'UserAvatarLink', name: 'UserAvatarLink',
components: { components: {
GlLink, UserAvatarLinkNew,
userAvatarImage, UserAvatarLinkOld,
},
directives: {
GlTooltip: GlTooltipDirective,
}, },
mixins: [glFeatureFlagMixin()],
props: { props: {
lazy: { lazy: {
type: Boolean, type: Boolean,
...@@ -76,36 +75,21 @@ export default { ...@@ -76,36 +75,21 @@ export default {
default: '', default: '',
}, },
}, },
computed: {
shouldShowUsername() {
return this.username.length > 0;
},
avatarTooltipText() {
return this.shouldShowUsername ? '' : this.tooltipText;
},
},
}; };
</script> </script>
<template> <template>
<gl-link :href="linkHref" class="user-avatar-link"> <user-avatar-link-new v-if="glFeatures.glAvatarForAllUserAvatars" v-bind="$props">
<user-avatar-image <slot></slot>
:img-src="imgSrc" <template #avatar-badge>
:img-alt="imgAlt" <slot name="avatar-badge"></slot>
:css-classes="imgCssClasses" </template>
:size="imgSize" </user-avatar-link-new>
:tooltip-text="avatarTooltipText"
:tooltip-placement="tooltipPlacement" <user-avatar-link-old v-else v-bind="$props">
:lazy="lazy" <slot></slot>
> <template #avatar-badge>
<slot></slot> </user-avatar-image <slot name="avatar-badge"></slot>
><span </template>
v-if="shouldShowUsername" </user-avatar-link-old>
v-gl-tooltip
:title="tooltipText"
:tooltip-placement="tooltipPlacement"
class="js-user-avatar-link-username"
>{{ username }}</span
><slot name="avatar-badge"></slot>
</gl-link>
</template> </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 ...@@ -170,7 +170,7 @@ RSpec.describe 'Epic show', :js do
page.within('.epic-page-container .detail-page-header-body') do page.within('.epic-page-container .detail-page-header-body') do
expect(find('.issuable-status-box > span')).to have_content('Open') expect(find('.issuable-status-box > span')).to have_content('Open')
expect(find('.issuable-meta')).to have_content('Created') 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
end end
......
...@@ -79,7 +79,7 @@ describe('Environment item', () => { ...@@ -79,7 +79,7 @@ describe('Environment item', () => {
describe('With user information', () => { describe('With user information', () => {
it('should render user avatar with link to profile', () => { 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, 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 { 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'; import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
import UserAvatarImageNew from '~/vue_shared/components/user_avatar/user_avatar_image_new.vue';
jest.mock('images/no_avatar.png', () => 'default-avatar-url'); import UserAvatarImageOld from '~/vue_shared/components/user_avatar/user_avatar_image_old.vue';
const PROVIDED_PROPS = { const PROVIDED_PROPS = {
size: 32, size: 32,
...@@ -15,10 +12,6 @@ const PROVIDED_PROPS = { ...@@ -15,10 +12,6 @@ const PROVIDED_PROPS = {
tooltipPlacement: 'bottom', tooltipPlacement: 'bottom',
}; };
const DEFAULT_PROPS = {
size: 20,
};
describe('User Avatar Image Component', () => { describe('User Avatar Image Component', () => {
let wrapper; let wrapper;
...@@ -26,174 +19,43 @@ describe('User Avatar Image Component', () => { ...@@ -26,174 +19,43 @@ describe('User Avatar Image Component', () => {
wrapper.destroy(); wrapper.destroy();
}); });
describe('`glAvatarForAllUserAvatars` feature flag enabled', () => { describe('when `glAvatarForAllUserAvatars` feature flag enabled', () => {
describe('Initialization', () => { beforeEach(() => {
beforeEach(() => { wrapper = shallowMount(UserAvatarImage, {
wrapper = shallowMount(UserAvatarImage, { propsData: {
propsData: { ...PROVIDED_PROPS,
...PROVIDED_PROPS, },
}, provide: {
provide: { glFeatures: {
glFeatures: { glAvatarForAllUserAvatars: true,
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,
}, },
}); },
});
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', () => { it('should render `UserAvatarImageNew` component', () => {
beforeEach(() => { expect(wrapper.findComponent(UserAvatarImageNew).exists()).toBe(true);
wrapper = shallowMount(UserAvatarImage); expect(wrapper.findComponent(UserAvatarImageOld).exists()).toBe(false);
});
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', () => { describe('when `glAvatarForAllUserAvatars` feature flag disabled', () => {
const props = PROVIDED_PROPS;
const slots = {
default: ['Action!'],
};
beforeEach(() => { beforeEach(() => {
wrapper = shallowMount(UserAvatarImage, { wrapper = shallowMount(UserAvatarImage, {
propsData: { props }, propsData: {
slots, ...PROVIDED_PROPS,
},
provide: {
glFeatures: {
glAvatarForAllUserAvatars: false,
},
},
}); });
}); });
it('renders the tooltip slot', () => { it('should render `UserAvatarImageOld` component', () => {
expect(wrapper.findComponent(GlTooltip).exists()).toBe(true); expect(wrapper.findComponent(UserAvatarImageNew).exists()).toBe(false);
}); expect(wrapper.findComponent(UserAvatarImageOld).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();
}); });
}); });
}); });
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 { 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 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', () => { describe('User Avatar Link Component', () => {
let wrapper; 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(() => { afterEach(() => {
wrapper.destroy(); 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(() => { beforeEach(() => {
createWrapper({ wrapper = shallowMount(UserAvatarLink, {
username: '', propsData: {
...PROVIDED_PROPS,
},
provide: {
glFeatures: {
glAvatarForAllUserAvatars: true,
},
},
}); });
}); });
it('should only render image tag in link', () => { it('should render `UserAvatarLinkNew` component', () => {
const childElements = wrapper.vm.$el.childNodes; expect(wrapper.findComponent(UserAvatarLinkNew).exists()).toBe(true);
expect(wrapper.findComponent(UserAvatarLinkOld).exists()).toBe(false);
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);
}); });
}); });
describe('username', () => { describe('when `glAvatarForAllUserAvatars` feature flag disabled', () => {
it('should not render avatar image tooltip', () => { beforeEach(() => {
expect(wrapper.find('.js-user-avatar-image-tooltip').exists()).toBe(false); wrapper = shallowMount(UserAvatarLink, {
}); propsData: {
...PROVIDED_PROPS,
it('should render username prop in <span>', () => { },
expect(trimText(wrapper.find('.js-user-avatar-link-username').text())).toEqual( provide: {
defaultProps.username, glFeatures: {
); glAvatarForAllUserAvatars: 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('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