Commit 62ad1a2f authored by Frédéric Caplette's avatar Frédéric Caplette

Merge branch '352508-gl-avatars-for-all-user-avatars' into 'master'

Render user avatar image using `GlAvatar`

See merge request gitlab-org/gitlab!81437
parents efaec9fc a231a01b
......@@ -6,25 +6,28 @@
Sample configuration:
<user-avatar-image
:lazy="true"
lazy
:img-src="userAvatarSrc"
:img-alt="tooltipText"
:tooltip-text="tooltipText"
tooltip-placement="top"
/>
*/
*/
import { GlTooltip } from '@gitlab/ui';
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';
export default {
name: 'UserAvatarImage',
components: {
GlTooltip,
GlAvatar,
},
mixins: [glFeatureFlagMixin()],
props: {
lazy: {
type: Boolean,
......@@ -85,7 +88,20 @@ export default {
<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,
......@@ -100,11 +116,9 @@ export default {
class="avatar"
/>
<gl-tooltip
v-if="tooltipText || $slots.default"
:target="() => $refs.userAvatarImage"
:placement="tooltipPlacement"
boundary="window"
class="js-user-avatar-image-tooltip"
>
<slot> {{ tooltipText }} </slot>
</gl-tooltip>
......
---
name: gl_avatar_for_all_user_avatars
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81437
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353477
milestone: '14.9'
type: development
group: group::foundations
default_enabled: false
......@@ -17,6 +17,7 @@ RSpec.describe 'Merge Requests > User resets approvers', :js do
before do
stub_licensed_features(multiple_approval_rules: true)
stub_feature_flags(gl_avatar_for_all_user_avatars: false)
project_approvers.each do |approver|
project.add_developer(approver)
......
......@@ -18,6 +18,7 @@ RSpec.describe 'Project settings > [EE] Merge Request Approvals', :js do
project.add_maintainer(user)
group.add_developer(user)
group.add_developer(group_member)
stub_feature_flags(gl_avatar_for_all_user_avatars: false)
end
it 'adds approver' do
......
......@@ -59,6 +59,7 @@ module Gitlab
push_frontend_feature_flag(:bootstrap_confirmation_modals, default_enabled: :yaml)
push_frontend_feature_flag(:sandboxed_mermaid, default_enabled: :yaml)
push_frontend_feature_flag(:source_editor_toolbar, default_enabled: :yaml)
push_frontend_feature_flag(:gl_avatar_for_all_user_avatars, default_enabled: :yaml)
end
# Exposes the state of a feature flag to the frontend code.
......
......@@ -23,6 +23,7 @@ RSpec.describe 'Project issue boards', :js do
project.add_maintainer(user2)
sign_in(user)
stub_feature_flags(gl_avatar_for_all_user_avatars: false)
set_cookie('sidebar_collapsed', 'true')
end
......
......@@ -25,6 +25,7 @@ RSpec.describe 'Merge request > User sees avatars on diff notes', :js do
before do
project.add_maintainer(user)
sign_in user
stub_feature_flags(gl_avatar_for_all_user_avatars: false)
set_cookie('sidebar_collapsed', 'true')
end
......
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');
const DEFAULT_PROPS = {
size: 99,
const PROVIDED_PROPS = {
size: 32,
imgSrc: 'myavatarurl.com',
imgAlt: 'mydisplayname',
cssClasses: 'myextraavatarclass',
......@@ -14,6 +15,10 @@ const DEFAULT_PROPS = {
tooltipPlacement: 'bottom',
};
const DEFAULT_PROPS = {
size: 20,
};
describe('User Avatar Image Component', () => {
let wrapper;
......@@ -21,11 +26,87 @@ 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: {
...DEFAULT_PROPS,
...PROVIDED_PROPS,
},
});
});
......@@ -34,14 +115,18 @@ describe('User Avatar Image Component', () => {
const imageElement = wrapper.find('img');
expect(imageElement.exists()).toBe(true);
expect(imageElement.attributes('src')).toBe(`${DEFAULT_PROPS.imgSrc}?width=99`);
expect(imageElement.attributes('data-src')).toBe(`${DEFAULT_PROPS.imgSrc}?width=99`);
expect(imageElement.attributes('alt')).toBe(DEFAULT_PROPS.imgAlt);
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(expect.arrayContaining(['avatar', 's99', DEFAULT_PROPS.cssClasses]));
expect(classes).toEqual(['avatar', 's32', PROVIDED_PROPS.cssClasses]);
expect(classes).not.toContain('lazy');
});
});
......@@ -50,7 +135,7 @@ describe('User Avatar Image Component', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage, {
propsData: {
...DEFAULT_PROPS,
...PROVIDED_PROPS,
lazy: true,
},
});
......@@ -61,7 +146,9 @@ describe('User Avatar Image Component', () => {
expect(imageElement.classes()).toContain('lazy');
expect(imageElement.attributes('src')).toBe(placeholderImage);
expect(imageElement.attributes('data-src')).toBe(`${DEFAULT_PROPS.imgSrc}?width=99`);
expect(imageElement.attributes('data-src')).toBe(
`${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
);
});
});
......@@ -73,12 +160,15 @@ describe('User Avatar Image Component', () => {
it('should have default avatar image', () => {
const imageElement = wrapper.find('img');
expect(imageElement.attributes('src')).toBe(`${defaultAvatarUrl}?width=20`);
expect(imageElement.attributes('src')).toBe(
`${defaultAvatarUrl}?width=${DEFAULT_PROPS.size}`,
);
});
});
});
describe('dynamic tooltip content', () => {
const props = DEFAULT_PROPS;
const props = PROVIDED_PROPS;
const slots = {
default: ['Action!'],
};
......@@ -91,11 +181,11 @@ describe('User Avatar Image Component', () => {
});
it('renders the tooltip slot', () => {
expect(wrapper.find('.js-user-avatar-image-tooltip').exists()).toBe(true);
expect(wrapper.findComponent(GlTooltip).exists()).toBe(true);
});
it('renders the tooltip content', () => {
expect(wrapper.find('.js-user-avatar-image-tooltip').text()).toContain(slots.default[0]);
expect(wrapper.findComponent(GlTooltip).text()).toContain(slots.default[0]);
});
it('does not render tooltip data attributes for on avatar image', () => {
......
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