Commit f1578b68 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '25807-status-tooltip-overlaps-extended-tooltip' into 'master'

Fix duplicate tooltip issue when hovering over status

See merge request gitlab-org/gitlab!29356
parents 2307f40e 2f06c7a1
...@@ -45,6 +45,13 @@ export default { ...@@ -45,6 +45,13 @@ export default {
default: true, default: true,
}, },
}, },
data() {
return {
isUsernameLinkHovered: false,
emojiTitle: '',
authorStatusHasTooltip: false,
};
},
computed: { computed: {
toggleChevronClass() { toggleChevronClass() {
return this.expanded ? 'fa-chevron-up' : 'fa-chevron-down'; return this.expanded ? 'fa-chevron-up' : 'fa-chevron-down';
...@@ -58,6 +65,28 @@ export default { ...@@ -58,6 +65,28 @@ export default {
showGitlabTeamMemberBadge() { showGitlabTeamMemberBadge() {
return this.author?.is_gitlab_employee; return this.author?.is_gitlab_employee;
}, },
authorLinkClasses() {
return {
hover: this.isUsernameLinkHovered,
'text-underline': this.isUsernameLinkHovered,
'author-name-link': true,
'js-user-link': true,
};
},
authorStatus() {
return this.author.status_tooltip_html;
},
emojiElement() {
return this.$refs?.authorStatus?.querySelector('gl-emoji');
},
},
mounted() {
this.emojiTitle = this.emojiElement ? this.emojiElement.getAttribute('title') : '';
const authorStatusTitle = this.$refs?.authorStatus
?.querySelector('.user-status-emoji')
?.getAttribute('title');
this.authorStatusHasTooltip = authorStatusTitle && authorStatusTitle !== '';
}, },
methods: { methods: {
...mapActions(['setTargetNoteHash']), ...mapActions(['setTargetNoteHash']),
...@@ -69,6 +98,20 @@ export default { ...@@ -69,6 +98,20 @@ export default {
this.setTargetNoteHash(this.noteTimestampLink); this.setTargetNoteHash(this.noteTimestampLink);
} }
}, },
removeEmojiTitle() {
this.emojiElement.removeAttribute('title');
},
addEmojiTitle() {
this.emojiElement.setAttribute('title', this.emojiTitle);
},
handleUsernameMouseEnter() {
this.$refs.authorNameLink.dispatchEvent(new Event('mouseenter'));
this.isUsernameLinkHovered = true;
},
handleUsernameMouseLeave() {
this.$refs.authorNameLink.dispatchEvent(new Event('mouseleave'));
this.isUsernameLinkHovered = false;
},
}, },
}; };
</script> </script>
...@@ -87,18 +130,34 @@ export default { ...@@ -87,18 +130,34 @@ export default {
</div> </div>
<template v-if="hasAuthor"> <template v-if="hasAuthor">
<a <a
v-once ref="authorNameLink"
:href="author.path" :href="author.path"
class="js-user-link" :class="authorLinkClasses"
:data-user-id="author.id" :data-user-id="author.id"
:data-username="author.username" :data-username="author.username"
> >
<slot name="note-header-info"></slot> <slot name="note-header-info"></slot>
<span class="note-header-author-name bold">{{ author.name }}</span> <span class="note-header-author-name bold">{{ author.name }}</span>
<span v-if="author.status_tooltip_html" v-html="author.status_tooltip_html"></span>
<span class="note-headline-light">@{{ author.username }}</span>
</a> </a>
<gitlab-team-member-badge v-if="showGitlabTeamMemberBadge" /> <span
v-if="authorStatus"
ref="authorStatus"
v-on="
authorStatusHasTooltip ? { mouseenter: removeEmojiTitle, mouseleave: addEmojiTitle } : {}
"
v-html="authorStatus"
></span>
<span class="text-nowrap author-username">
<a
ref="authorUsernameLink"
class="author-username-link"
:href="author.path"
@mouseenter="handleUsernameMouseEnter"
@mouseleave="handleUsernameMouseLeave"
><span class="note-headline-light">@{{ author.username }}</span>
</a>
<gitlab-team-member-badge v-if="showGitlabTeamMemberBadge" />
</span>
</template> </template>
<span v-else>{{ __('A deleted user') }}</span> <span v-else>{{ __('A deleted user') }}</span>
<span class="note-headline-light note-headline-meta"> <span class="note-headline-light note-headline-meta">
......
...@@ -588,7 +588,8 @@ $note-form-margin-left: 72px; ...@@ -588,7 +588,8 @@ $note-form-margin-left: 72px;
a { a {
color: inherit; color: inherit;
&:hover { &:hover,
&.hover {
color: $blue-600; color: $blue-600;
} }
...@@ -605,6 +606,21 @@ $note-form-margin-left: 72px; ...@@ -605,6 +606,21 @@ $note-form-margin-left: 72px;
.author-link { .author-link {
color: $gl-text-color; color: $gl-text-color;
} }
// Prevent flickering of link when hovering between `author-name-link` and `.author-username-link`
.author-name-link + .author-username .author-username-link {
position: relative;
&::before {
content: '';
position: absolute;
right: 100%;
width: 0.25rem;
height: 100%;
top: 0;
bottom: 0;
}
}
} }
.discussion-header { .discussion-header {
......
---
title: Prevent duplicate tooltips when hovering over status emoji in comments
merge_request: 29356
author:
type: fixed
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import NoteHeader from '~/notes/components/note_header.vue'; import NoteHeader from '~/notes/components/note_header.vue';
import GitlabTeamMemberBadge from '~/vue_shared/components/user_avatar/badges/gitlab_team_member_badge.vue'; import GitlabTeamMemberBadge from '~/vue_shared/components/user_avatar/badges/gitlab_team_member_badge.vue';
...@@ -179,4 +180,70 @@ describe('NoteHeader component', () => { ...@@ -179,4 +180,70 @@ describe('NoteHeader component', () => {
expect(findTimestamp().exists()).toBe(true); expect(findTimestamp().exists()).toBe(true);
}); });
}); });
describe('author username link', () => {
it('proxies `mouseenter` event to author name link', () => {
createComponent({ author });
const dispatchEvent = jest.spyOn(wrapper.vm.$refs.authorNameLink, 'dispatchEvent');
wrapper.find({ ref: 'authorUsernameLink' }).trigger('mouseenter');
expect(dispatchEvent).toHaveBeenCalledWith(new Event('mouseenter'));
});
it('proxies `mouseleave` event to author name link', () => {
createComponent({ author });
const dispatchEvent = jest.spyOn(wrapper.vm.$refs.authorNameLink, 'dispatchEvent');
wrapper.find({ ref: 'authorUsernameLink' }).trigger('mouseleave');
expect(dispatchEvent).toHaveBeenCalledWith(new Event('mouseleave'));
});
});
describe('when author status tooltip is opened', () => {
it('removes `title` attribute from emoji to prevent duplicate tooltips', () => {
createComponent({
author: {
...author,
status_tooltip_html:
'"<span class="user-status-emoji has-tooltip" title="foo bar" data-html="true" data-placement="top"><gl-emoji title="basketball and hoop" data-name="basketball" data-unicode-version="6.0">🏀</gl-emoji></span>"',
},
});
return nextTick().then(() => {
const authorStatus = wrapper.find({ ref: 'authorStatus' });
authorStatus.trigger('mouseenter');
expect(authorStatus.find('gl-emoji').attributes('title')).toBeUndefined();
});
});
});
describe('when author username link is hovered', () => {
it('toggles hover specific CSS classes on author name link', done => {
createComponent({ author });
const authorUsernameLink = wrapper.find({ ref: 'authorUsernameLink' });
const authorNameLink = wrapper.find({ ref: 'authorNameLink' });
authorUsernameLink.trigger('mouseenter');
nextTick(() => {
expect(authorNameLink.classes()).toContain('hover');
expect(authorNameLink.classes()).toContain('text-underline');
authorUsernameLink.trigger('mouseleave');
nextTick(() => {
expect(authorNameLink.classes()).not.toContain('hover');
expect(authorNameLink.classes()).not.toContain('text-underline');
done();
});
});
});
});
}); });
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