Commit 348aebfa authored by Phil Hughes's avatar Phil Hughes

Merge branch '254997-fix-labels-search-scroll' into 'master'

Fix Vue Labels Select dropdown keyboard scroll

See merge request gitlab-org/gitlab!43874
parents 5afa19f9 b060e23f
...@@ -3,5 +3,3 @@ export const DropdownVariant = { ...@@ -3,5 +3,3 @@ export const DropdownVariant = {
Standalone: 'standalone', Standalone: 'standalone',
Embedded: 'embedded', Embedded: 'embedded',
}; };
export const LIST_BUFFER_SIZE = 5;
<script> <script>
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapGetters, mapActions } from 'vuex';
import { GlLoadingIcon, GlButton, GlSearchBoxByType, GlLink } from '@gitlab/ui'; import {
GlIntersectionObserver,
GlLoadingIcon,
GlButton,
GlSearchBoxByType,
GlLink,
} from '@gitlab/ui';
import fuzzaldrinPlus from 'fuzzaldrin-plus'; import fuzzaldrinPlus from 'fuzzaldrin-plus';
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes'; import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
import LabelItem from './label_item.vue'; import LabelItem from './label_item.vue';
import { LIST_BUFFER_SIZE } from './constants';
export default { export default {
LIST_BUFFER_SIZE,
components: { components: {
GlIntersectionObserver,
GlLoadingIcon, GlLoadingIcon,
GlButton, GlButton,
GlSearchBoxByType, GlSearchBoxByType,
GlLink, GlLink,
SmartVirtualList,
LabelItem, LabelItem,
}, },
data() { data() {
...@@ -46,15 +48,8 @@ export default { ...@@ -46,15 +48,8 @@ export default {
} }
return this.labels; return this.labels;
}, },
showListContainer() {
if (this.isDropdownVariantSidebar) {
return !this.labelsFetchInProgress;
}
return true;
},
showNoMatchingResultsMessage() { showNoMatchingResultsMessage() {
return !this.labelsFetchInProgress && !this.visibleLabels.length; return Boolean(this.searchKey) && this.visibleLabels.length === 0;
}, },
}, },
watch: { watch: {
...@@ -67,14 +62,12 @@ export default { ...@@ -67,14 +62,12 @@ export default {
} }
}, },
}, },
mounted() {
this.fetchLabels();
},
methods: { methods: {
...mapActions([ ...mapActions([
'toggleDropdownContents', 'toggleDropdownContents',
'toggleDropdownContentsCreateView', 'toggleDropdownContentsCreateView',
'fetchLabels', 'fetchLabels',
'receiveLabelsSuccess',
'updateSelectedLabels', 'updateSelectedLabels',
'toggleDropdownContents', 'toggleDropdownContents',
]), ]),
...@@ -99,6 +92,17 @@ export default { ...@@ -99,6 +92,17 @@ export default {
} }
} }
}, },
/**
* We want to remove loaded labels to ensure component
* fetches fresh set of labels every time when shown.
*/
handleComponentDisappear() {
this.receiveLabelsSuccess([]);
},
handleCreateLabelClick() {
this.receiveLabelsSuccess([]);
this.toggleDropdownContentsCreateView();
},
/** /**
* This method enables keyboard navigation support for * This method enables keyboard navigation support for
* the dropdown. * the dropdown.
...@@ -135,84 +139,75 @@ export default { ...@@ -135,84 +139,75 @@ export default {
</script> </script>
<template> <template>
<div class="labels-select-contents-list js-labels-list" @keydown="handleKeyDown"> <gl-intersection-observer @appear="fetchLabels" @disappear="handleComponentDisappear">
<gl-loading-icon <div class="labels-select-contents-list js-labels-list" @keydown="handleKeyDown">
v-if="labelsFetchInProgress" <div
class="labels-fetch-loading position-absolute gl-display-flex gl-align-items-center w-100 h-100" v-if="isDropdownVariantSidebar || isDropdownVariantEmbedded"
size="md" class="dropdown-title gl-display-flex gl-align-items-center gl-pt-0 gl-pb-3!"
/> data-testid="dropdown-title"
<div
v-if="isDropdownVariantSidebar || isDropdownVariantEmbedded"
class="dropdown-title gl-display-flex gl-align-items-center gl-pt-0 gl-pb-3!"
data-testid="dropdown-title"
>
<span class="flex-grow-1">{{ labelsListTitle }}</span>
<gl-button
:aria-label="__('Close')"
variant="link"
size="small"
class="dropdown-header-button gl-p-0!"
icon="close"
@click="toggleDropdownContents"
/>
</div>
<div class="dropdown-input" @click.stop="() => {}">
<gl-search-box-by-type
v-model="searchKey"
:autofocus="true"
data-qa-selector="dropdown_input_field"
/>
</div>
<div
v-show="showListContainer"
ref="labelsListContainer"
class="dropdown-content"
data-testid="dropdown-content"
>
<smart-virtual-list
:length="visibleLabels.length"
:remain="$options.LIST_BUFFER_SIZE"
:size="$options.LIST_BUFFER_SIZE"
wclass="list-unstyled mb-0"
wtag="ul"
class="h-100"
> >
<li v-for="(label, index) in visibleLabels" :key="label.id" class="d-block text-left"> <span class="flex-grow-1">{{ labelsListTitle }}</span>
<gl-button
:aria-label="__('Close')"
variant="link"
size="small"
class="dropdown-header-button gl-p-0!"
icon="close"
@click="toggleDropdownContents"
/>
</div>
<div class="dropdown-input" @click.stop="() => {}">
<gl-search-box-by-type
v-model="searchKey"
:autofocus="true"
:disabled="labelsFetchInProgress"
data-qa-selector="dropdown_input_field"
/>
</div>
<div ref="labelsListContainer" class="dropdown-content" data-testid="dropdown-content">
<gl-loading-icon
v-if="labelsFetchInProgress"
class="labels-fetch-loading gl-align-items-center w-100 h-100"
size="md"
/>
<ul v-else class="list-unstyled mb-0">
<label-item <label-item
v-for="(label, index) in visibleLabels"
:key="label.id"
:label="label" :label="label"
:is-label-set="label.set" :is-label-set="label.set"
:highlight="index === currentHighlightItem" :highlight="index === currentHighlightItem"
@clickLabel="handleLabelClick(label)" @clickLabel="handleLabelClick(label)"
/> />
</li> <li v-show="showNoMatchingResultsMessage" class="gl-p-3 gl-text-center">
<li v-show="showNoMatchingResultsMessage" class="gl-p-3 gl-text-center"> {{ __('No matching results') }}
{{ __('No matching results') }} </li>
</li> </ul>
</smart-virtual-list> </div>
</div> <div
<div v-if="isDropdownVariantSidebar || isDropdownVariantEmbedded"
v-if="isDropdownVariantSidebar || isDropdownVariantEmbedded" class="dropdown-footer"
class="dropdown-footer" data-testid="dropdown-footer"
data-testid="dropdown-footer" >
> <ul class="list-unstyled">
<ul class="list-unstyled"> <li v-if="allowLabelCreate">
<li v-if="allowLabelCreate"> <gl-link
<gl-link class="gl-display-flex w-100 flex-row text-break-word label-item"
class="gl-display-flex w-100 flex-row text-break-word label-item" @click="handleCreateLabelClick"
@click="toggleDropdownContentsCreateView" >
> {{ footerCreateLabelTitle }}
{{ footerCreateLabelTitle }} </gl-link>
</gl-link> </li>
</li> <li>
<li> <gl-link
<gl-link :href="labelsManagePath"
:href="labelsManagePath" class="gl-display-flex flex-row text-break-word label-item"
class="gl-display-flex flex-row text-break-word label-item" >
> {{ footerManageLabelTitle }}
{{ footerManageLabelTitle }} </gl-link>
</gl-link> </li>
</li> </ul>
</ul> </div>
</div> </div>
</div> </gl-intersection-observer>
</template> </template>
<script> <script>
import { GlIcon, GlLink } from '@gitlab/ui'; import { GlLink, GlIcon } from '@gitlab/ui';
export default { export default {
components: { functional: true,
GlIcon,
GlLink,
},
props: { props: {
label: { label: {
type: Object, type: Object,
...@@ -21,46 +18,65 @@ export default { ...@@ -21,46 +18,65 @@ export default {
default: false, default: false,
}, },
}, },
data() { render(h, { props, listeners }) {
return { const { label, highlight, isLabelSet } = props;
isSet: this.isLabelSet,
}; const labelColorBox = h('span', {
}, class: 'dropdown-label-box',
computed: { style: {
labelBoxStyle() { backgroundColor: label.color,
return { },
backgroundColor: this.label.color, attrs: {
}; 'data-testid': 'label-color-box',
}, },
}, });
watch: {
/** const checkedIcon = h(GlIcon, {
* This watcher assures that if user used class: {
* `Enter` key to set/unset label, changes 'mr-2 align-self-center': true,
* are reflected here too. hidden: !isLabelSet,
*/ },
isLabelSet(value) { props: {
this.isSet = value; name: 'mobile-issue-close',
}, },
}, });
methods: {
handleClick() { const noIcon = h('span', {
this.isSet = !this.isSet; class: {
this.$emit('clickLabel', this.label); 'mr-3 pr-2': true,
}, hidden: isLabelSet,
},
attrs: {
'data-testid': 'no-icon',
},
});
const labelTitle = h('span', label.title);
const labelLink = h(
GlLink,
{
class: 'd-flex align-items-baseline text-break-word label-item',
on: {
click: () => {
listeners.clickLabel(label);
},
},
},
[noIcon, checkedIcon, labelColorBox, labelTitle],
);
return h(
'li',
{
class: {
'd-block': true,
'text-left': true,
'is-focused': highlight,
},
},
[labelLink],
);
}, },
}; };
</script> </script>
<template>
<gl-link
class="d-flex align-items-baseline text-break-word label-item"
:class="{ 'is-focused': highlight }"
@click="handleClick"
>
<gl-icon v-show="isSet" name="mobile-issue-close" class="mr-2 align-self-center" />
<span v-show="!isSet" data-testid="no-icon" class="mr-3 pr-2"></span>
<span class="dropdown-label-box" data-testid="label-color-box" :style="labelBoxStyle"></span>
<span>{{ label.title }}</span>
</gl-link>
</template>
...@@ -266,7 +266,7 @@ export default { ...@@ -266,7 +266,7 @@ export default {
</dropdown-value> </dropdown-value>
<dropdown-button v-show="dropdownButtonVisible" class="gl-mt-2" /> <dropdown-button v-show="dropdownButtonVisible" class="gl-mt-2" />
<dropdown-contents <dropdown-contents
v-if="dropdownButtonVisible && showDropdownContents" v-show="dropdownButtonVisible && showDropdownContents"
ref="dropdownContents" ref="dropdownContents"
/> />
</template> </template>
......
...@@ -1017,6 +1017,23 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu { ...@@ -1017,6 +1017,23 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu {
} }
} }
li {
&:hover,
&.is-focused {
.label-item {
@include dropdown-item-hover;
text-decoration: none;
}
}
}
.labels-select-dropdown-button {
.gl-button-text {
width: 100%;
}
}
.labels-select-dropdown-contents { .labels-select-dropdown-contents {
min-height: $dropdown-min-height; min-height: $dropdown-min-height;
max-height: 330px; max-height: 330px;
...@@ -1050,13 +1067,6 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu { ...@@ -1050,13 +1067,6 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu {
.label-item { .label-item {
padding: 8px 20px; padding: 8px 20px;
&:hover,
&.is-focused {
@include dropdown-item-hover;
text-decoration: none;
}
} }
.color-input-container { .color-input-container {
......
---
title: Fix Vue Labels Select dropdown keyboard scroll
merge_request: 43874
author:
type: fixed
...@@ -149,6 +149,7 @@ export default { ...@@ -149,6 +149,7 @@ export default {
<sidebar-todo <sidebar-todo
v-show="sidebarCollapsed && isUserSignedIn" v-show="sidebarCollapsed && isUserSignedIn"
:sidebar-collapsed="sidebarCollapsed" :sidebar-collapsed="sidebarCollapsed"
data-testid="todo"
/> />
<sidebar-date-picker <sidebar-date-picker
v-show="!sidebarCollapsed" v-show="!sidebarCollapsed"
...@@ -167,6 +168,7 @@ export default { ...@@ -167,6 +168,7 @@ export default {
:date-from-milestones="startDateTimeFromMilestones" :date-from-milestones="startDateTimeFromMilestones"
:selected-date="startDateTime" :selected-date="startDateTime"
:is-date-invalid="isDateInvalid" :is-date-invalid="isDateInvalid"
data-testid="start-date"
block-class="start-date" block-class="start-date"
@toggleCollapse="toggleSidebar({ sidebarCollapsed })" @toggleCollapse="toggleSidebar({ sidebarCollapsed })"
@toggleDateType="changeStartDateType" @toggleDateType="changeStartDateType"
...@@ -188,6 +190,7 @@ export default { ...@@ -188,6 +190,7 @@ export default {
:date-from-milestones="dueDateTimeFromMilestones" :date-from-milestones="dueDateTimeFromMilestones"
:selected-date="dueDateTime" :selected-date="dueDateTime"
:is-date-invalid="isDateInvalid" :is-date-invalid="isDateInvalid"
data-testid="due-date"
block-class="due-date" block-class="due-date"
@toggleDateType="changeDueDateType" @toggleDateType="changeDueDateType"
@saveDate="saveDueDate" @saveDate="saveDueDate"
...@@ -199,9 +202,13 @@ export default { ...@@ -199,9 +202,13 @@ export default {
:max-date="dueDateForCollapsedSidebar" :max-date="dueDateForCollapsedSidebar"
@toggleCollapse="toggleSidebar({ sidebarCollapsed })" @toggleCollapse="toggleSidebar({ sidebarCollapsed })"
/> />
<sidebar-labels :can-update="canUpdate" :sidebar-collapsed="sidebarCollapsed" /> <sidebar-labels
:can-update="canUpdate"
:sidebar-collapsed="sidebarCollapsed"
data-testid="labels-select"
/>
<div v-if="allowSubEpics" class="block ancestors"> <div v-if="allowSubEpics" class="block ancestors">
<ancestors-tree :ancestors="ancestors" :is-fetching="false" /> <ancestors-tree :ancestors="ancestors" :is-fetching="false" data-testid="ancestors" />
</div> </div>
<confidential-issue-sidebar <confidential-issue-sidebar
...@@ -216,7 +223,7 @@ export default { ...@@ -216,7 +223,7 @@ export default {
@toggleSidebar="toggleSidebar({ sidebarCollapsed })" @toggleSidebar="toggleSidebar({ sidebarCollapsed })"
/> />
</div> </div>
<sidebar-subscription :sidebar-collapsed="sidebarCollapsed" /> <sidebar-subscription :sidebar-collapsed="sidebarCollapsed" data-testid="subscribe" />
</div> </div>
</aside> </aside>
</template> </template>
...@@ -22,6 +22,7 @@ describe('SidebarLabelsComponent', () => { ...@@ -22,6 +22,7 @@ describe('SidebarLabelsComponent', () => {
propsData: { canUpdate: false, sidebarCollapsed: false }, propsData: { canUpdate: false, sidebarCollapsed: false },
store, store,
stubs: { stubs: {
LabelsSelectVue: true,
GlLabel: true, GlLabel: true,
}, },
}); });
......
import Vuex from 'vuex'; import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlButton, GlLoadingIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui'; import {
GlIntersectionObserver,
GlButton,
GlLoadingIcon,
GlSearchBoxByType,
GlLink,
} from '@gitlab/ui';
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes'; import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
import DropdownContentsLabelsView from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue'; import DropdownContentsLabelsView from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue';
import LabelItem from '~/vue_shared/components/sidebar/labels_select_vue/label_item.vue'; import LabelItem from '~/vue_shared/components/sidebar/labels_select_vue/label_item.vue';
...@@ -88,20 +93,25 @@ describe('DropdownContentsLabelsView', () => { ...@@ -88,20 +93,25 @@ describe('DropdownContentsLabelsView', () => {
}); });
}); });
describe('showListContainer', () => { describe('showNoMatchingResultsMessage', () => {
it.each` it.each`
variant | loading | showList searchKey | labels | labelsDescription | returnValue
${'sidebar'} | ${false} | ${true} ${''} | ${[]} | ${'empty'} | ${false}
${'sidebar'} | ${true} | ${false} ${'bug'} | ${[]} | ${'empty'} | ${true}
${'not-sidebar'} | ${true} | ${true} ${''} | ${mockLabels} | ${'not empty'} | ${false}
${'not-sidebar'} | ${false} | ${true} ${'bug'} | ${mockLabels} | ${'not empty'} | ${false}
`( `(
'returns $showList if `state.variant` is "$variant" and `labelsFetchInProgress` is $loading', 'returns $returnValue when searchKey is "$searchKey" and visibleLabels is $labelsDescription',
({ variant, loading, showList }) => { async ({ searchKey, labels, returnValue }) => {
createComponent({ ...mockConfig, variant }); wrapper.setData({
wrapper.vm.$store.state.labelsFetchInProgress = loading; searchKey,
});
expect(wrapper.vm.showListContainer).toBe(showList); wrapper.vm.$store.dispatch('receiveLabelsSuccess', labels);
await wrapper.vm.$nextTick();
expect(wrapper.vm.showNoMatchingResultsMessage).toBe(returnValue);
}, },
); );
}); });
...@@ -118,6 +128,28 @@ describe('DropdownContentsLabelsView', () => { ...@@ -118,6 +128,28 @@ describe('DropdownContentsLabelsView', () => {
}); });
}); });
describe('handleComponentDisappear', () => {
it('calls action `receiveLabelsSuccess` with empty array', () => {
jest.spyOn(wrapper.vm, 'receiveLabelsSuccess');
wrapper.vm.handleComponentDisappear();
expect(wrapper.vm.receiveLabelsSuccess).toHaveBeenCalledWith([]);
});
});
describe('handleCreateLabelClick', () => {
it('calls actions `receiveLabelsSuccess` with empty array and `toggleDropdownContentsCreateView`', () => {
jest.spyOn(wrapper.vm, 'receiveLabelsSuccess');
jest.spyOn(wrapper.vm, 'toggleDropdownContentsCreateView');
wrapper.vm.handleCreateLabelClick();
expect(wrapper.vm.receiveLabelsSuccess).toHaveBeenCalledWith([]);
expect(wrapper.vm.toggleDropdownContentsCreateView).toHaveBeenCalled();
});
});
describe('handleKeyDown', () => { describe('handleKeyDown', () => {
it('decreases `currentHighlightItem` value by 1 when Up arrow key is pressed', () => { it('decreases `currentHighlightItem` value by 1 when Up arrow key is pressed', () => {
wrapper.setData({ wrapper.setData({
...@@ -226,8 +258,8 @@ describe('DropdownContentsLabelsView', () => { ...@@ -226,8 +258,8 @@ describe('DropdownContentsLabelsView', () => {
}); });
describe('template', () => { describe('template', () => {
it('renders component container element with class `labels-select-contents-list`', () => { it('renders gl-intersection-observer as component root', () => {
expect(wrapper.attributes('class')).toContain('labels-select-contents-list'); expect(wrapper.find(GlIntersectionObserver).exists()).toBe(true);
}); });
it('renders gl-loading-icon component when `labelsFetchInProgress` prop is true', () => { it('renders gl-loading-icon component when `labelsFetchInProgress` prop is true', () => {
...@@ -272,15 +304,11 @@ describe('DropdownContentsLabelsView', () => { ...@@ -272,15 +304,11 @@ describe('DropdownContentsLabelsView', () => {
expect(searchInputEl.attributes('autofocus')).toBe('true'); expect(searchInputEl.attributes('autofocus')).toBe('true');
}); });
it('renders smart-virtual-list element', () => {
expect(wrapper.find(SmartVirtualList).exists()).toBe(true);
});
it('renders label elements for all labels', () => { it('renders label elements for all labels', () => {
expect(wrapper.findAll(LabelItem)).toHaveLength(mockLabels.length); expect(wrapper.findAll(LabelItem)).toHaveLength(mockLabels.length);
}); });
it('renders label element with "is-focused" when value of `currentHighlightItem` is more than -1', () => { it('renders label element with `highlight` set to true when value of `currentHighlightItem` is more than -1', () => {
wrapper.setData({ wrapper.setData({
currentHighlightItem: 0, currentHighlightItem: 0,
}); });
...@@ -288,7 +316,7 @@ describe('DropdownContentsLabelsView', () => { ...@@ -288,7 +316,7 @@ describe('DropdownContentsLabelsView', () => {
return wrapper.vm.$nextTick(() => { return wrapper.vm.$nextTick(() => {
const labelItemEl = findDropdownContent().find(LabelItem); const labelItemEl = findDropdownContent().find(LabelItem);
expect(labelItemEl.props('highlight')).toBe(true); expect(labelItemEl.attributes('highlight')).toBe('true');
}); });
}); });
...@@ -310,9 +338,12 @@ describe('DropdownContentsLabelsView', () => { ...@@ -310,9 +338,12 @@ describe('DropdownContentsLabelsView', () => {
return wrapper.vm.$nextTick(() => { return wrapper.vm.$nextTick(() => {
const dropdownContent = findDropdownContent(); const dropdownContent = findDropdownContent();
const loadingIcon = findLoadingIcon();
expect(dropdownContent.exists()).toBe(true); expect(dropdownContent.exists()).toBe(true);
expect(dropdownContent.isVisible()).toBe(false); expect(dropdownContent.isVisible()).toBe(true);
expect(loadingIcon.exists()).toBe(true);
expect(loadingIcon.isVisible()).toBe(true);
}); });
}); });
......
...@@ -6,11 +6,15 @@ import { mockRegularLabel } from './mock_data'; ...@@ -6,11 +6,15 @@ import { mockRegularLabel } from './mock_data';
const mockLabel = { ...mockRegularLabel, set: true }; const mockLabel = { ...mockRegularLabel, set: true };
const createComponent = ({ label = mockLabel, highlight = true } = {}) => const createComponent = ({
label = mockLabel,
isLabelSet = mockLabel.set,
highlight = true,
} = {}) =>
shallowMount(LabelItem, { shallowMount(LabelItem, {
propsData: { propsData: {
label, label,
isLabelSet: label.set, isLabelSet,
highlight, highlight,
}, },
}); });
...@@ -26,94 +30,44 @@ describe('LabelItem', () => { ...@@ -26,94 +30,44 @@ describe('LabelItem', () => {
wrapper.destroy(); wrapper.destroy();
}); });
describe('computed', () => {
describe('labelBoxStyle', () => {
it('returns an object containing `backgroundColor` based on `label` prop', () => {
expect(wrapper.vm.labelBoxStyle).toEqual(
expect.objectContaining({
backgroundColor: mockLabel.color,
}),
);
});
});
});
describe('watchers', () => {
describe('isLabelSet', () => {
it('sets value of `isLabelSet` to `isSet` data prop', () => {
expect(wrapper.vm.isSet).toBe(true);
wrapper.setProps({
isLabelSet: false,
});
return wrapper.vm.$nextTick(() => {
expect(wrapper.vm.isSet).toBe(false);
});
});
});
});
describe('methods', () => {
describe('handleClick', () => {
it('sets value of `isSet` data prop to opposite of its current value', () => {
wrapper.setData({
isSet: true,
});
wrapper.vm.handleClick();
expect(wrapper.vm.isSet).toBe(false);
wrapper.vm.handleClick();
expect(wrapper.vm.isSet).toBe(true);
});
it('emits event `clickLabel` on component with `label` prop as param', () => {
wrapper.vm.handleClick();
expect(wrapper.emitted('clickLabel')).toBeTruthy();
expect(wrapper.emitted('clickLabel')[0]).toEqual([mockLabel]);
});
});
});
describe('template', () => { describe('template', () => {
it('renders gl-link component', () => { it('renders gl-link component', () => {
expect(wrapper.find(GlLink).exists()).toBe(true); expect(wrapper.find(GlLink).exists()).toBe(true);
}); });
it('renders gl-link component with class `is-focused` when `highlight` prop is true', () => { it('renders component root with class `is-focused` when `highlight` prop is true', () => {
wrapper.setProps({ const wrapperTemp = createComponent({
highlight: true, highlight: true,
}); });
return wrapper.vm.$nextTick(() => { expect(wrapperTemp.classes()).toContain('is-focused');
expect(wrapper.find(GlLink).classes()).toContain('is-focused');
}); wrapperTemp.destroy();
}); });
it('renders visible gl-icon component when `isSet` prop is true', () => { it('renders visible gl-icon component when `isLabelSet` prop is true', () => {
wrapper.setData({ const wrapperTemp = createComponent({
isSet: true, isLabelSet: true,
}); });
return wrapper.vm.$nextTick(() => { const iconEl = wrapperTemp.find(GlIcon);
const iconEl = wrapper.find(GlIcon);
expect(iconEl.isVisible()).toBe(true); expect(iconEl.isVisible()).toBe(true);
expect(iconEl.props('name')).toBe('mobile-issue-close'); expect(iconEl.props('name')).toBe('mobile-issue-close');
});
wrapperTemp.destroy();
}); });
it('renders visible span element as placeholder instead of gl-icon when `isSet` prop is false', () => { it('renders visible span element as placeholder instead of gl-icon when `isLabelSet` prop is false', () => {
wrapper.setData({ const wrapperTemp = createComponent({
isSet: false, isLabelSet: false,
}); });
return wrapper.vm.$nextTick(() => { const placeholderEl = wrapperTemp.find('[data-testid="no-icon"]');
const placeholderEl = wrapper.find('[data-testid="no-icon"]');
expect(placeholderEl.isVisible()).toBe(true); expect(placeholderEl.isVisible()).toBe(true);
});
wrapperTemp.destroy();
}); });
it('renders label color element', () => { it('renders label color element', () => {
......
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