Commit 52bdc432 authored by Scott Hampton's avatar Scott Hampton

Merge branch 'eread/migrate-awards-list-buttons' into 'master'

Migrate awards list buttons to new buttons

See merge request gitlab-org/gitlab!43061
parents 18aa64a8 d92bdee7
<script> <script>
/* eslint-disable vue/no-v-html */ /* eslint-disable vue/no-v-html */
import { groupBy } from 'lodash'; import { groupBy } from 'lodash';
import { GlIcon, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui'; import { GlIcon, GlButton, GlTooltipDirective } from '@gitlab/ui';
import { glEmojiTag } from '../../emoji'; import { glEmojiTag } from '../../emoji';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
...@@ -10,8 +10,8 @@ const NO_USER_ID = -1; ...@@ -10,8 +10,8 @@ const NO_USER_ID = -1;
export default { export default {
components: { components: {
GlButton,
GlIcon, GlIcon,
GlLoadingIcon,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -64,7 +64,7 @@ export default { ...@@ -64,7 +64,7 @@ export default {
methods: { methods: {
getAwardClassBindings(awardList) { getAwardClassBindings(awardList) {
return { return {
active: this.hasReactionByCurrentUser(awardList), selected: this.hasReactionByCurrentUser(awardList),
disabled: this.currentUserId === NO_USER_ID, disabled: this.currentUserId === NO_USER_ID,
}; };
}, },
...@@ -150,40 +150,39 @@ export default { ...@@ -150,40 +150,39 @@ export default {
<template> <template>
<div class="awards js-awards-block"> <div class="awards js-awards-block">
<button <gl-button
v-for="awardList in groupedAwards" v-for="awardList in groupedAwards"
:key="awardList.name" :key="awardList.name"
v-gl-tooltip.viewport v-gl-tooltip.viewport
class="gl-mr-3"
:class="awardList.classes" :class="awardList.classes"
:title="awardList.title" :title="awardList.title"
data-testid="award-button" data-testid="award-button"
class="btn award-control"
type="button"
@click="handleAward(awardList.name)" @click="handleAward(awardList.name)"
> >
<span data-testid="award-html" v-html="awardList.html"></span> <template #emoji>
<span class="award-control-text js-counter">{{ awardList.list.length }}</span> <span class="award-emoji-block" data-testid="award-html" v-html="awardList.html"></span>
</button> </template>
<span class="js-counter">{{ awardList.list.length }}</span>
</gl-button>
<div v-if="canAwardEmoji" class="award-menu-holder"> <div v-if="canAwardEmoji" class="award-menu-holder">
<button <gl-button
v-gl-tooltip.viewport v-gl-tooltip.viewport
:class="addButtonClass" :class="addButtonClass"
class="award-control btn js-add-award" class="add-reaction-button js-add-award"
title="Add reaction" title="Add reaction"
:aria-label="__('Add reaction')" :aria-label="__('Add reaction')"
type="button"
> >
<span class="award-control-icon award-control-icon-neutral"> <span class="reaction-control-icon reaction-control-icon-neutral">
<gl-icon aria-hidden="true" name="slight-smile" /> <gl-icon aria-hidden="true" name="slight-smile" />
</span> </span>
<span class="award-control-icon award-control-icon-positive"> <span class="reaction-control-icon reaction-control-icon-positive">
<gl-icon aria-hidden="true" name="smiley" /> <gl-icon aria-hidden="true" name="smiley" />
</span> </span>
<span class="award-control-icon award-control-icon-super-positive"> <span class="reaction-control-icon reaction-control-icon-super-positive">
<gl-icon aria-hidden="true" name="smiley" /> <gl-icon aria-hidden="true" name="smile" />
</span> </span>
<gl-loading-icon size="md" color="dark" class="award-control-icon-loading" /> </gl-button>
</button>
</div> </div>
</div> </div>
</template> </template>
...@@ -253,3 +253,111 @@ ...@@ -253,3 +253,111 @@
vertical-align: middle; vertical-align: middle;
} }
} }
// The following encompasses the "add reaction" button redesign to
// align properly within GitLab UI's gl-button. The implementation
// above will be deprecated once all instances of "award emoji" are
// migrated to Vue.
.gl-button .award-emoji-block gl-emoji {
top: -1px;
margin-top: -1px;
margin-bottom: -1px;
}
.add-reaction-button {
position: relative;
// This forces the height and width of the inner content to match
// other gl-buttons despite all child elements being set to
// `position:absolute`
&::after {
content: '\a0';
width: 1em;
}
.reaction-control-icon {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
// center the icon vertically and horizontally within the button
display: flex;
align-items: center;
justify-content: center;
@include transition(opacity, transform);
.gl-icon {
height: $default-icon-size;
width: $default-icon-size;
}
}
.reaction-control-icon-neutral {
opacity: 1;
}
.reaction-control-icon-positive,
.reaction-control-icon-super-positive {
opacity: 0;
}
&:hover,
&.active,
&:active,
&.is-active {
// extra specificty added to override another selector
.reaction-control-icon .gl-icon {
color: $blue-500;
transform: scale(1.15);
}
.reaction-control-icon-neutral {
opacity: 0;
}
}
&:hover {
.reaction-control-icon-positive {
opacity: 1;
}
}
&.active,
&:active,
&.is-active {
.reaction-control-icon-positive {
opacity: 0;
}
.reaction-control-icon-super-positive {
opacity: 1;
}
}
&.disabled {
cursor: default;
&:hover,
&:focus,
&:active {
.reaction-control-icon .gl-icon {
color: inherit;
transform: scale(1);
}
.reaction-control-icon-neutral {
opacity: 1;
}
.reaction-control-icon-positive,
.reaction-control-icon-super-positive {
opacity: 0;
}
}
}
}
---
title: Migrate awards list buttons to new buttons
merge_request: 43061
author:
type: other
...@@ -5,12 +5,17 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` ...@@ -5,12 +5,17 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
class="awards js-awards-block" class="awards js-awards-block"
> >
<button <button
class="btn award-control" class="btn gl-mr-3 btn-default btn-md gl-button"
data-testid="award-button" data-testid="award-button"
title="Ada, Leonardo, and Marie" title="Ada, Leonardo, and Marie"
type="button" type="button"
> >
<!---->
<!---->
<span <span
class="award-emoji-block"
data-testid="award-html" data-testid="award-html"
> >
...@@ -23,18 +28,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` ...@@ -23,18 +28,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span> </span>
<span <span
class="award-control-text js-counter" class="gl-button-text"
>
<span
class="js-counter"
> >
3 3
</span> </span>
</span>
</button> </button>
<button <button
class="btn award-control active" class="btn gl-mr-3 btn-default btn-md gl-button selected"
data-testid="award-button" data-testid="award-button"
title="You, Ada, and Marie" title="You, Ada, and Marie"
type="button" type="button"
> >
<!---->
<!---->
<span <span
class="award-emoji-block"
data-testid="award-html" data-testid="award-html"
> >
...@@ -47,18 +62,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` ...@@ -47,18 +62,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span> </span>
<span <span
class="award-control-text js-counter" class="gl-button-text"
>
<span
class="js-counter"
> >
3 3
</span> </span>
</span>
</button> </button>
<button <button
class="btn award-control" class="btn gl-mr-3 btn-default btn-md gl-button"
data-testid="award-button" data-testid="award-button"
title="Ada and Jane" title="Ada and Jane"
type="button" type="button"
> >
<!---->
<!---->
<span <span
class="award-emoji-block"
data-testid="award-html" data-testid="award-html"
> >
...@@ -71,18 +96,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` ...@@ -71,18 +96,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span> </span>
<span <span
class="award-control-text js-counter" class="gl-button-text"
>
<span
class="js-counter"
> >
2 2
</span> </span>
</span>
</button> </button>
<button <button
class="btn award-control active" class="btn gl-mr-3 btn-default btn-md gl-button selected"
data-testid="award-button" data-testid="award-button"
title="You, Ada, Jane, and Leonardo" title="You, Ada, Jane, and Leonardo"
type="button" type="button"
> >
<!---->
<!---->
<span <span
class="award-emoji-block"
data-testid="award-html" data-testid="award-html"
> >
...@@ -95,18 +130,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` ...@@ -95,18 +130,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span> </span>
<span <span
class="award-control-text js-counter" class="gl-button-text"
>
<span
class="js-counter"
> >
4 4
</span> </span>
</span>
</button> </button>
<button <button
class="btn award-control active" class="btn gl-mr-3 btn-default btn-md gl-button selected"
data-testid="award-button" data-testid="award-button"
title="You" title="You"
type="button" type="button"
> >
<!---->
<!---->
<span <span
class="award-emoji-block"
data-testid="award-html" data-testid="award-html"
> >
...@@ -119,18 +164,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` ...@@ -119,18 +164,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span> </span>
<span <span
class="award-control-text js-counter" class="gl-button-text"
>
<span
class="js-counter"
> >
1 1
</span> </span>
</span>
</button> </button>
<button <button
class="btn award-control" class="btn gl-mr-3 btn-default btn-md gl-button"
data-testid="award-button" data-testid="award-button"
title="Marie" title="Marie"
type="button" type="button"
> >
<!---->
<!---->
<span <span
class="award-emoji-block"
data-testid="award-html" data-testid="award-html"
> >
...@@ -143,18 +198,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` ...@@ -143,18 +198,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span> </span>
<span <span
class="award-control-text js-counter" class="gl-button-text"
>
<span
class="js-counter"
> >
1 1
</span> </span>
</span>
</button> </button>
<button <button
class="btn award-control active" class="btn gl-mr-3 btn-default btn-md gl-button selected"
data-testid="award-button" data-testid="award-button"
title="You" title="You"
type="button" type="button"
> >
<!---->
<!---->
<span <span
class="award-emoji-block"
data-testid="award-html" data-testid="award-html"
> >
...@@ -167,10 +232,15 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` ...@@ -167,10 +232,15 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span> </span>
<span <span
class="award-control-text js-counter" class="gl-button-text"
>
<span
class="js-counter"
> >
1 1
</span> </span>
</span>
</button> </button>
<div <div
...@@ -178,46 +248,59 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` ...@@ -178,46 +248,59 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
> >
<button <button
aria-label="Add reaction" aria-label="Add reaction"
class="award-control btn js-add-award js-test-add-button-class" class="btn add-reaction-button js-add-award btn-default btn-md gl-button js-test-add-button-class"
title="Add reaction" title="Add reaction"
type="button" type="button"
>
<!---->
<!---->
<span
class="gl-button-text"
> >
<span <span
class="award-control-icon award-control-icon-neutral" class="reaction-control-icon reaction-control-icon-neutral"
> >
<gl-icon-stub <svg
aria-hidden="true" aria-hidden="true"
name="slight-smile" class="gl-icon s16"
size="16" data-testid="slight-smile-icon"
>
<use
href="#slight-smile"
/> />
</svg>
</span> </span>
<span <span
class="award-control-icon award-control-icon-positive" class="reaction-control-icon reaction-control-icon-positive"
> >
<gl-icon-stub <svg
aria-hidden="true" aria-hidden="true"
name="smiley" class="gl-icon s16"
size="16" data-testid="smiley-icon"
>
<use
href="#smiley"
/> />
</svg>
</span> </span>
<span <span
class="award-control-icon award-control-icon-super-positive" class="reaction-control-icon reaction-control-icon-super-positive"
> >
<gl-icon-stub <svg
aria-hidden="true" aria-hidden="true"
name="smiley" class="gl-icon s16"
size="16" data-testid="smile-icon"
>
<use
href="#smile"
/> />
</svg>
</span>
</span> </span>
<gl-loading-icon-stub
class="award-control-icon-loading"
color="dark"
label="Loading"
size="md"
/>
</button> </button>
</div> </div>
</div> </div>
......
import { shallowMount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import AwardsList from '~/vue_shared/components/awards_list.vue'; import AwardsList from '~/vue_shared/components/awards_list.vue';
const createUser = (id, name) => ({ id, name }); const createUser = (id, name) => ({ id, name });
...@@ -41,6 +41,8 @@ const TEST_AWARDS = [ ...@@ -41,6 +41,8 @@ const TEST_AWARDS = [
]; ];
const TEST_ADD_BUTTON_CLASS = 'js-test-add-button-class'; const TEST_ADD_BUTTON_CLASS = 'js-test-add-button-class';
const REACTION_CONTROL_CLASSES = ['btn', 'gl-mr-3', 'btn-default', 'btn-md', 'gl-button'];
describe('vue_shared/components/awards_list', () => { describe('vue_shared/components/awards_list', () => {
let wrapper; let wrapper;
...@@ -54,16 +56,16 @@ describe('vue_shared/components/awards_list', () => { ...@@ -54,16 +56,16 @@ describe('vue_shared/components/awards_list', () => {
throw new Error('There should only be one wrapper created per test'); throw new Error('There should only be one wrapper created per test');
} }
wrapper = shallowMount(AwardsList, { propsData: props }); wrapper = mount(AwardsList, { propsData: props });
}; };
const matchingEmojiTag = name => expect.stringMatching(`gl-emoji data-name="${name}"`); const matchingEmojiTag = name => expect.stringMatching(`gl-emoji data-name="${name}"`);
const findAwardButtons = () => wrapper.findAll('[data-testid="award-button"'); const findAwardButtons = () => wrapper.findAll('[data-testid="award-button"]');
const findAwardsData = () => const findAwardsData = () =>
findAwardButtons().wrappers.map(x => { findAwardButtons().wrappers.map(x => {
return { return {
classes: x.classes(), classes: x.classes(),
title: x.attributes('title'), title: x.attributes('title'),
html: x.find('[data-testid="award-html"]').element.innerHTML, html: x.find('[data-testid="award-html"]').html(),
count: Number(x.find('.js-counter').text()), count: Number(x.find('.js-counter').text()),
}; };
}); });
...@@ -86,43 +88,43 @@ describe('vue_shared/components/awards_list', () => { ...@@ -86,43 +88,43 @@ describe('vue_shared/components/awards_list', () => {
it('shows awards in correct order', () => { it('shows awards in correct order', () => {
expect(findAwardsData()).toEqual([ expect(findAwardsData()).toEqual([
{ {
classes: ['btn', 'award-control'], classes: REACTION_CONTROL_CLASSES,
count: 3, count: 3,
html: matchingEmojiTag(EMOJI_THUMBSUP), html: matchingEmojiTag(EMOJI_THUMBSUP),
title: 'Ada, Leonardo, and Marie', title: 'Ada, Leonardo, and Marie',
}, },
{ {
classes: ['btn', 'award-control', 'active'], classes: [...REACTION_CONTROL_CLASSES, 'selected'],
count: 3, count: 3,
html: matchingEmojiTag(EMOJI_THUMBSDOWN), html: matchingEmojiTag(EMOJI_THUMBSDOWN),
title: 'You, Ada, and Marie', title: 'You, Ada, and Marie',
}, },
{ {
classes: ['btn', 'award-control'], classes: REACTION_CONTROL_CLASSES,
count: 2, count: 2,
html: matchingEmojiTag(EMOJI_SMILE), html: matchingEmojiTag(EMOJI_SMILE),
title: 'Ada and Jane', title: 'Ada and Jane',
}, },
{ {
classes: ['btn', 'award-control', 'active'], classes: [...REACTION_CONTROL_CLASSES, 'selected'],
count: 4, count: 4,
html: matchingEmojiTag(EMOJI_OK), html: matchingEmojiTag(EMOJI_OK),
title: 'You, Ada, Jane, and Leonardo', title: 'You, Ada, Jane, and Leonardo',
}, },
{ {
classes: ['btn', 'award-control', 'active'], classes: [...REACTION_CONTROL_CLASSES, 'selected'],
count: 1, count: 1,
html: matchingEmojiTag(EMOJI_CACTUS), html: matchingEmojiTag(EMOJI_CACTUS),
title: 'You', title: 'You',
}, },
{ {
classes: ['btn', 'award-control'], classes: REACTION_CONTROL_CLASSES,
count: 1, count: 1,
html: matchingEmojiTag(EMOJI_A), html: matchingEmojiTag(EMOJI_A),
title: 'Marie', title: 'Marie',
}, },
{ {
classes: ['btn', 'award-control', 'active'], classes: [...REACTION_CONTROL_CLASSES, 'selected'],
count: 1, count: 1,
html: matchingEmojiTag(EMOJI_B), html: matchingEmojiTag(EMOJI_B),
title: 'You', title: 'You',
...@@ -135,7 +137,7 @@ describe('vue_shared/components/awards_list', () => { ...@@ -135,7 +137,7 @@ describe('vue_shared/components/awards_list', () => {
findAwardButtons() findAwardButtons()
.at(2) .at(2)
.trigger('click'); .vm.$emit('click');
expect(wrapper.emitted().award).toEqual([[EMOJI_SMILE]]); expect(wrapper.emitted().award).toEqual([[EMOJI_SMILE]]);
}); });
...@@ -162,7 +164,7 @@ describe('vue_shared/components/awards_list', () => { ...@@ -162,7 +164,7 @@ describe('vue_shared/components/awards_list', () => {
findAwardButtons() findAwardButtons()
.at(0) .at(0)
.trigger('click'); .vm.$emit('click');
expect(wrapper.emitted().award).toEqual([[Number(EMOJI_100)]]); expect(wrapper.emitted().award).toEqual([[Number(EMOJI_100)]]);
}); });
...@@ -225,26 +227,26 @@ describe('vue_shared/components/awards_list', () => { ...@@ -225,26 +227,26 @@ describe('vue_shared/components/awards_list', () => {
it('shows awards in correct order', () => { it('shows awards in correct order', () => {
expect(findAwardsData()).toEqual([ expect(findAwardsData()).toEqual([
{ {
classes: ['btn', 'award-control'], classes: REACTION_CONTROL_CLASSES,
count: 0, count: 0,
html: matchingEmojiTag(EMOJI_THUMBSUP), html: matchingEmojiTag(EMOJI_THUMBSUP),
title: '', title: '',
}, },
{ {
classes: ['btn', 'award-control'], classes: REACTION_CONTROL_CLASSES,
count: 0, count: 0,
html: matchingEmojiTag(EMOJI_THUMBSDOWN), html: matchingEmojiTag(EMOJI_THUMBSDOWN),
title: '', title: '',
}, },
// We expect the EMOJI_100 before the EMOJI_SMILE because it was given as a defaultAward // We expect the EMOJI_100 before the EMOJI_SMILE because it was given as a defaultAward
{ {
classes: ['btn', 'award-control'], classes: REACTION_CONTROL_CLASSES,
count: 1, count: 1,
html: matchingEmojiTag(EMOJI_100), html: matchingEmojiTag(EMOJI_100),
title: 'Marie', title: 'Marie',
}, },
{ {
classes: ['btn', 'award-control'], classes: REACTION_CONTROL_CLASSES,
count: 1, count: 1,
html: matchingEmojiTag(EMOJI_SMILE), html: matchingEmojiTag(EMOJI_SMILE),
title: 'Marie', title: 'Marie',
......
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