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>
/* eslint-disable vue/no-v-html */
import { groupBy } from 'lodash';
import { GlIcon, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import { GlIcon, GlButton, GlTooltipDirective } from '@gitlab/ui';
import { glEmojiTag } from '../../emoji';
import { __, sprintf } from '~/locale';
......@@ -10,8 +10,8 @@ const NO_USER_ID = -1;
export default {
components: {
GlButton,
GlIcon,
GlLoadingIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
......@@ -64,7 +64,7 @@ export default {
methods: {
getAwardClassBindings(awardList) {
return {
active: this.hasReactionByCurrentUser(awardList),
selected: this.hasReactionByCurrentUser(awardList),
disabled: this.currentUserId === NO_USER_ID,
};
},
......@@ -150,40 +150,39 @@ export default {
<template>
<div class="awards js-awards-block">
<button
<gl-button
v-for="awardList in groupedAwards"
:key="awardList.name"
v-gl-tooltip.viewport
class="gl-mr-3"
:class="awardList.classes"
:title="awardList.title"
data-testid="award-button"
class="btn award-control"
type="button"
@click="handleAward(awardList.name)"
>
<span data-testid="award-html" v-html="awardList.html"></span>
<span class="award-control-text js-counter">{{ awardList.list.length }}</span>
</button>
<template #emoji>
<span class="award-emoji-block" data-testid="award-html" v-html="awardList.html"></span>
</template>
<span class="js-counter">{{ awardList.list.length }}</span>
</gl-button>
<div v-if="canAwardEmoji" class="award-menu-holder">
<button
<gl-button
v-gl-tooltip.viewport
:class="addButtonClass"
class="award-control btn js-add-award"
class="add-reaction-button js-add-award"
title="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" />
</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" />
</span>
<span class="award-control-icon award-control-icon-super-positive">
<gl-icon aria-hidden="true" name="smiley" />
<span class="reaction-control-icon reaction-control-icon-super-positive">
<gl-icon aria-hidden="true" name="smile" />
</span>
<gl-loading-icon size="md" color="dark" class="award-control-icon-loading" />
</button>
</gl-button>
</div>
</div>
</template>
......@@ -253,3 +253,111 @@
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`] = `
class="awards js-awards-block"
>
<button
class="btn award-control"
class="btn gl-mr-3 btn-default btn-md gl-button"
data-testid="award-button"
title="Ada, Leonardo, and Marie"
type="button"
>
<!---->
<!---->
<span
class="award-emoji-block"
data-testid="award-html"
>
......@@ -23,18 +28,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
<span
class="award-control-text js-counter"
class="gl-button-text"
>
3
<span
class="js-counter"
>
3
</span>
</span>
</button>
<button
class="btn award-control active"
class="btn gl-mr-3 btn-default btn-md gl-button selected"
data-testid="award-button"
title="You, Ada, and Marie"
type="button"
>
<!---->
<!---->
<span
class="award-emoji-block"
data-testid="award-html"
>
......@@ -47,18 +62,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
<span
class="award-control-text js-counter"
class="gl-button-text"
>
3
<span
class="js-counter"
>
3
</span>
</span>
</button>
<button
class="btn award-control"
class="btn gl-mr-3 btn-default btn-md gl-button"
data-testid="award-button"
title="Ada and Jane"
type="button"
>
<!---->
<!---->
<span
class="award-emoji-block"
data-testid="award-html"
>
......@@ -71,18 +96,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
<span
class="award-control-text js-counter"
class="gl-button-text"
>
2
<span
class="js-counter"
>
2
</span>
</span>
</button>
<button
class="btn award-control active"
class="btn gl-mr-3 btn-default btn-md gl-button selected"
data-testid="award-button"
title="You, Ada, Jane, and Leonardo"
type="button"
>
<!---->
<!---->
<span
class="award-emoji-block"
data-testid="award-html"
>
......@@ -95,18 +130,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
<span
class="award-control-text js-counter"
class="gl-button-text"
>
4
<span
class="js-counter"
>
4
</span>
</span>
</button>
<button
class="btn award-control active"
class="btn gl-mr-3 btn-default btn-md gl-button selected"
data-testid="award-button"
title="You"
type="button"
>
<!---->
<!---->
<span
class="award-emoji-block"
data-testid="award-html"
>
......@@ -119,18 +164,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
<span
class="award-control-text js-counter"
class="gl-button-text"
>
1
<span
class="js-counter"
>
1
</span>
</span>
</button>
<button
class="btn award-control"
class="btn gl-mr-3 btn-default btn-md gl-button"
data-testid="award-button"
title="Marie"
type="button"
>
<!---->
<!---->
<span
class="award-emoji-block"
data-testid="award-html"
>
......@@ -143,18 +198,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
<span
class="award-control-text js-counter"
class="gl-button-text"
>
1
<span
class="js-counter"
>
1
</span>
</span>
</button>
<button
class="btn award-control active"
class="btn gl-mr-3 btn-default btn-md gl-button selected"
data-testid="award-button"
title="You"
type="button"
>
<!---->
<!---->
<span
class="award-emoji-block"
data-testid="award-html"
>
......@@ -167,9 +232,14 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
<span
class="award-control-text js-counter"
class="gl-button-text"
>
1
<span
class="js-counter"
>
1
</span>
</span>
</button>
......@@ -178,46 +248,59 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
>
<button
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"
type="button"
>
<span
class="award-control-icon award-control-icon-neutral"
>
<gl-icon-stub
aria-hidden="true"
name="slight-smile"
size="16"
/>
</span>
<!---->
<!---->
<span
class="award-control-icon award-control-icon-positive"
class="gl-button-text"
>
<gl-icon-stub
aria-hidden="true"
name="smiley"
size="16"
/>
<span
class="reaction-control-icon reaction-control-icon-neutral"
>
<svg
aria-hidden="true"
class="gl-icon s16"
data-testid="slight-smile-icon"
>
<use
href="#slight-smile"
/>
</svg>
</span>
<span
class="reaction-control-icon reaction-control-icon-positive"
>
<svg
aria-hidden="true"
class="gl-icon s16"
data-testid="smiley-icon"
>
<use
href="#smiley"
/>
</svg>
</span>
<span
class="reaction-control-icon reaction-control-icon-super-positive"
>
<svg
aria-hidden="true"
class="gl-icon s16"
data-testid="smile-icon"
>
<use
href="#smile"
/>
</svg>
</span>
</span>
<span
class="award-control-icon award-control-icon-super-positive"
>
<gl-icon-stub
aria-hidden="true"
name="smiley"
size="16"
/>
</span>
<gl-loading-icon-stub
class="award-control-icon-loading"
color="dark"
label="Loading"
size="md"
/>
</button>
</div>
</div>
......
import { shallowMount } from '@vue/test-utils';
import { mount } from '@vue/test-utils';
import AwardsList from '~/vue_shared/components/awards_list.vue';
const createUser = (id, name) => ({ id, name });
......@@ -41,6 +41,8 @@ const TEST_AWARDS = [
];
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', () => {
let wrapper;
......@@ -54,16 +56,16 @@ describe('vue_shared/components/awards_list', () => {
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 findAwardButtons = () => wrapper.findAll('[data-testid="award-button"');
const findAwardButtons = () => wrapper.findAll('[data-testid="award-button"]');
const findAwardsData = () =>
findAwardButtons().wrappers.map(x => {
return {
classes: x.classes(),
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()),
};
});
......@@ -86,43 +88,43 @@ describe('vue_shared/components/awards_list', () => {
it('shows awards in correct order', () => {
expect(findAwardsData()).toEqual([
{
classes: ['btn', 'award-control'],
classes: REACTION_CONTROL_CLASSES,
count: 3,
html: matchingEmojiTag(EMOJI_THUMBSUP),
title: 'Ada, Leonardo, and Marie',
},
{
classes: ['btn', 'award-control', 'active'],
classes: [...REACTION_CONTROL_CLASSES, 'selected'],
count: 3,
html: matchingEmojiTag(EMOJI_THUMBSDOWN),
title: 'You, Ada, and Marie',
},
{
classes: ['btn', 'award-control'],
classes: REACTION_CONTROL_CLASSES,
count: 2,
html: matchingEmojiTag(EMOJI_SMILE),
title: 'Ada and Jane',
},
{
classes: ['btn', 'award-control', 'active'],
classes: [...REACTION_CONTROL_CLASSES, 'selected'],
count: 4,
html: matchingEmojiTag(EMOJI_OK),
title: 'You, Ada, Jane, and Leonardo',
},
{
classes: ['btn', 'award-control', 'active'],
classes: [...REACTION_CONTROL_CLASSES, 'selected'],
count: 1,
html: matchingEmojiTag(EMOJI_CACTUS),
title: 'You',
},
{
classes: ['btn', 'award-control'],
classes: REACTION_CONTROL_CLASSES,
count: 1,
html: matchingEmojiTag(EMOJI_A),
title: 'Marie',
},
{
classes: ['btn', 'award-control', 'active'],
classes: [...REACTION_CONTROL_CLASSES, 'selected'],
count: 1,
html: matchingEmojiTag(EMOJI_B),
title: 'You',
......@@ -135,7 +137,7 @@ describe('vue_shared/components/awards_list', () => {
findAwardButtons()
.at(2)
.trigger('click');
.vm.$emit('click');
expect(wrapper.emitted().award).toEqual([[EMOJI_SMILE]]);
});
......@@ -162,7 +164,7 @@ describe('vue_shared/components/awards_list', () => {
findAwardButtons()
.at(0)
.trigger('click');
.vm.$emit('click');
expect(wrapper.emitted().award).toEqual([[Number(EMOJI_100)]]);
});
......@@ -225,26 +227,26 @@ describe('vue_shared/components/awards_list', () => {
it('shows awards in correct order', () => {
expect(findAwardsData()).toEqual([
{
classes: ['btn', 'award-control'],
classes: REACTION_CONTROL_CLASSES,
count: 0,
html: matchingEmojiTag(EMOJI_THUMBSUP),
title: '',
},
{
classes: ['btn', 'award-control'],
classes: REACTION_CONTROL_CLASSES,
count: 0,
html: matchingEmojiTag(EMOJI_THUMBSDOWN),
title: '',
},
// 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,
html: matchingEmojiTag(EMOJI_100),
title: 'Marie',
},
{
classes: ['btn', 'award-control'],
classes: REACTION_CONTROL_CLASSES,
count: 1,
html: matchingEmojiTag(EMOJI_SMILE),
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