Commit f31df951 authored by Martin Wortschack's avatar Martin Wortschack

Merge branch 'kp-add-variant-support-vue-labels-dropdown' into 'master'

Add variants & single select support in labels dropdown

See merge request gitlab-org/gitlab!28419
parents 1e8928cf f8ca9879
// eslint-disable-next-line import/prefer-default-export
export const DropdownVariant = {
Sidebar: 'sidebar',
Standalone: 'standalone',
};
<script>
import { mapGetters } from 'vuex';
import { GlDeprecatedButton, GlIcon } from '@gitlab/ui';
import { mapActions, mapGetters } from 'vuex';
import { GlButton, GlIcon } from '@gitlab/ui';
export default {
components: {
GlDeprecatedButton,
GlButton,
GlIcon,
},
computed: {
...mapGetters(['dropdownButtonText']),
...mapGetters(['dropdownButtonText', 'isDropdownVariantStandalone']),
},
methods: {
...mapActions(['toggleDropdownContents']),
handleButtonClick(e) {
if (this.isDropdownVariantStandalone) {
this.toggleDropdownContents();
e.stopPropagation();
}
},
},
};
</script>
<template>
<gl-deprecated-button class="labels-select-dropdown-button w-100 text-left">
<span class="dropdown-toggle-text">{{ dropdownButtonText }}</span>
<gl-button
class="labels-select-dropdown-button js-dropdown-button w-100 text-left"
@click="handleButtonClick"
>
<span class="dropdown-toggle-text" :class="{ 'flex-fill': isDropdownVariantStandalone }">{{
dropdownButtonText
}}</span>
<gl-icon name="chevron-down" class="pull-right" />
</gl-deprecated-button>
</gl-button>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import {
GlTooltipDirective,
GlDeprecatedButton,
GlIcon,
GlFormInput,
GlLink,
GlLoadingIcon,
} from '@gitlab/ui';
import { GlTooltipDirective, GlButton, GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui';
export default {
components: {
GlDeprecatedButton,
GlIcon,
GlButton,
GlFormInput,
GlLink,
GlLoadingIcon,
......@@ -60,25 +52,23 @@ export default {
<template>
<div class="labels-select-contents-create js-labels-create">
<div class="dropdown-title d-flex align-items-center pt-0 pb-2">
<gl-deprecated-button
<gl-button
:aria-label="__('Go back')"
variant="link"
size="sm"
size="small"
class="js-btn-back dropdown-header-button p-0"
icon="arrow-left"
@click="toggleDropdownContentsCreateView"
>
<gl-icon name="arrow-left" />
</gl-deprecated-button>
/>
<span class="flex-grow-1">{{ labelsCreateTitle }}</span>
<gl-deprecated-button
<gl-button
:aria-label="__('Close')"
variant="link"
size="sm"
size="small"
class="dropdown-header-button p-0"
icon="close"
@click="toggleDropdownContents"
>
<gl-icon name="close" />
</gl-deprecated-button>
/>
</div>
<div class="dropdown-input">
<gl-form-input
......@@ -107,21 +97,19 @@ export default {
</div>
</div>
<div class="dropdown-actions clearfix pt-2 px-2">
<gl-deprecated-button
<gl-button
:disabled="disableCreate"
variant="primary"
category="primary"
variant="success"
class="pull-left d-flex align-items-center"
@click="handleCreateClick"
>
<gl-loading-icon v-show="labelCreateInProgress" :inline="true" class="mr-1" />
{{ __('Create') }}
</gl-deprecated-button>
<gl-deprecated-button
class="pull-right js-btn-cancel-create"
@click="toggleDropdownContentsCreateView"
>
</gl-button>
<gl-button class="pull-right js-btn-cancel-create" @click="toggleDropdownContentsCreateView">
{{ __('Cancel') }}
</gl-deprecated-button>
</gl-button>
</div>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import { GlLoadingIcon, GlDeprecatedButton, GlIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui';
import { GlLoadingIcon, GlButton, GlIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui';
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
export default {
components: {
GlLoadingIcon,
GlDeprecatedButton,
GlButton,
GlIcon,
GlSearchBoxByType,
GlLink,
......@@ -20,6 +20,8 @@ export default {
},
computed: {
...mapState([
'allowLabelCreate',
'allowMultiselect',
'labelsManagePath',
'labels',
'labelsFetchInProgress',
......@@ -27,7 +29,7 @@ export default {
'footerCreateLabelTitle',
'footerManageLabelTitle',
]),
...mapGetters(['selectedLabelsList']),
...mapGetters(['selectedLabelsList', 'isDropdownVariantSidebar']),
visibleLabels() {
if (this.searchKey) {
return this.labels.filter(label =>
......@@ -56,6 +58,7 @@ export default {
'toggleDropdownContentsCreateView',
'fetchLabels',
'updateSelectedLabels',
'toggleDropdownContents',
]),
getDropdownLabelBoxStyle(label) {
return {
......@@ -111,6 +114,7 @@ export default {
},
handleLabelClick(label) {
this.updateSelectedLabels([label]);
if (!this.allowMultiselect) this.toggleDropdownContents();
},
},
};
......@@ -123,17 +127,16 @@ export default {
class="labels-fetch-loading position-absolute d-flex align-items-center w-100 h-100"
size="md"
/>
<div class="dropdown-title d-flex align-items-center pt-0 pb-2">
<div v-if="isDropdownVariantSidebar" class="dropdown-title d-flex align-items-center pt-0 pb-2">
<span class="flex-grow-1">{{ labelsListTitle }}</span>
<gl-deprecated-button
<gl-button
:aria-label="__('Close')"
variant="link"
size="sm"
size="small"
class="dropdown-header-button p-0"
icon="close"
@click="toggleDropdownContents"
>
<gl-icon name="close" />
</gl-deprecated-button>
/>
</div>
<div class="dropdown-input">
<gl-search-box-by-type v-model="searchKey" :autofocus="true" />
......@@ -157,14 +160,13 @@ export default {
</li>
</ul>
</div>
<div class="dropdown-footer">
<div v-if="isDropdownVariantSidebar" class="dropdown-footer">
<ul class="list-unstyled">
<li>
<gl-deprecated-button
variant="link"
<li v-if="allowLabelCreate">
<gl-link
class="d-flex w-100 flex-row text-break-word label-item"
@click="toggleDropdownContentsCreateView"
>{{ footerCreateLabelTitle }}</gl-deprecated-button
>{{ footerCreateLabelTitle }}</gl-link
>
</li>
<li>
......
<script>
import $ from 'jquery';
import Vue from 'vue';
import Vuex, { mapState, mapActions } from 'vuex';
import Vuex, { mapState, mapActions, mapGetters } from 'vuex';
import { __ } from '~/locale';
import DropdownValueCollapsed from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue';
......@@ -13,6 +13,8 @@ import DropdownValue from './dropdown_value.vue';
import DropdownButton from './dropdown_button.vue';
import DropdownContents from './dropdown_contents.vue';
import { DropdownVariant } from './constants';
Vue.use(Vuex);
export default {
......@@ -33,14 +35,19 @@ export default {
type: Boolean,
required: true,
},
allowMultiselect: {
type: Boolean,
required: false,
default: false,
},
allowScopedLabels: {
type: Boolean,
required: true,
},
dropdownOnly: {
type: Boolean,
variant: {
type: String,
required: false,
default: false,
default: DropdownVariant.Sidebar,
},
selectedLabels: {
type: Array,
......@@ -90,6 +97,10 @@ export default {
},
computed: {
...mapState(['showDropdownButton', 'showDropdownContents']),
...mapGetters(['isDropdownVariantSidebar', 'isDropdownVariantStandalone']),
dropdownButtonVisible() {
return this.isDropdownVariantSidebar ? this.showDropdownButton : true;
},
},
watch: {
selectedLabels(selectedLabels) {
......@@ -100,9 +111,10 @@ export default {
},
mounted() {
this.setInitialState({
dropdownOnly: this.dropdownOnly,
variant: this.variant,
allowLabelEdit: this.allowLabelEdit,
allowLabelCreate: this.allowLabelCreate,
allowMultiselect: this.allowMultiselect,
allowScopedLabels: this.allowScopedLabels,
selectedLabels: this.selectedLabels,
labelsFetchPath: this.labelsFetchPath,
......@@ -148,13 +160,20 @@ export default {
// as the dropdown wrapper is not using `GlDropdown` as
// it will also require us to use `BDropdownForm`
// which is yet to be implemented in GitLab UI.
const hasExceptionClass = [
'js-dropdown-button',
'js-btn-cancel-create',
'js-sidebar-dropdown-toggle',
].some(className => target?.classList.contains(className));
const hadExceptionParent = ['.js-btn-back', '.js-labels-list'].some(
className => $(target).parents(className).length,
);
if (
this.showDropdownButton &&
this.showDropdownContents &&
!$(target).parents('.js-btn-back').length &&
!$(target).parents('.js-labels-list').length &&
!target?.classList.contains('js-btn-cancel-create') &&
!target?.classList.contains('js-sidebar-dropdown-toggle') &&
!hadExceptionParent &&
!hasExceptionClass &&
!this.$refs.dropdownButtonCollapsed?.$el.contains(target) &&
!this.$refs.dropdownContents?.$el.contains(target)
) {
......@@ -175,10 +194,12 @@ export default {
</script>
<template>
<div class="labels-select-wrapper position-relative">
<div v-if="!dropdownOnly">
<div
class="labels-select-wrapper position-relative"
:class="{ 'is-standalone': isDropdownVariantStandalone }"
>
<template v-if="isDropdownVariantSidebar">
<dropdown-value-collapsed
v-if="allowLabelCreate"
ref="dropdownButtonCollapsed"
:labels="selectedLabels"
@onValueClick="handleCollapsedValueClick"
......@@ -190,8 +211,18 @@ export default {
<dropdown-value v-show="!showDropdownButton">
<slot></slot>
</dropdown-value>
<dropdown-button v-show="showDropdownButton" />
<dropdown-contents v-if="showDropdownButton && showDropdownContents" ref="dropdownContents" />
</div>
<dropdown-button v-show="dropdownButtonVisible" />
<dropdown-contents
v-if="dropdownButtonVisible && showDropdownContents"
ref="dropdownContents"
/>
</template>
<template v-if="isDropdownVariantStandalone">
<dropdown-button v-show="dropdownButtonVisible" />
<dropdown-contents
v-if="dropdownButtonVisible && showDropdownContents"
ref="dropdownContents"
/>
</template>
</div>
</template>
import { __, s__, sprintf } from '~/locale';
import { DropdownVariant } from '../constants';
/**
* Returns string representing current labels
......@@ -6,8 +7,11 @@ import { __, s__, sprintf } from '~/locale';
*
* @param {object} state
*/
export const dropdownButtonText = state => {
const selectedLabels = state.labels.filter(label => label.set);
export const dropdownButtonText = (state, getters) => {
const selectedLabels = getters.isDropdownVariantSidebar
? state.labels.filter(label => label.set)
: state.selectedLabels;
if (!selectedLabels.length) {
return __('Label');
} else if (selectedLabels.length > 1) {
......@@ -26,5 +30,19 @@ export const dropdownButtonText = state => {
*/
export const selectedLabelsList = state => state.selectedLabels.map(label => label.id);
/**
* Returns boolean representing whether dropdown variant
* is `sidebar`
* @param {object} state
*/
export const isDropdownVariantSidebar = state => state.variant === DropdownVariant.Sidebar;
/**
* Returns boolean representing whether dropdown variant
* is `standalone`
* @param {object} state
*/
export const isDropdownVariantStandalone = state => state.variant === DropdownVariant.Standalone;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
import * as types from './mutation_types';
import { DropdownVariant } from '../constants';
export default {
[types.SET_INITIAL_STATE](state, props) {
......@@ -10,7 +11,7 @@ export default {
},
[types.TOGGLE_DROPDOWN_CONTENTS](state) {
if (!state.dropdownOnly) {
if (state.variant === DropdownVariant.Sidebar) {
state.showDropdownButton = !state.showDropdownButton;
}
state.showDropdownContents = !state.showDropdownContents;
......@@ -68,7 +69,16 @@ export default {
set: !label.set,
});
} else {
allLabels.push(label);
// In case multiselect is not allowed
// we unselect any existing selected label
const unchangedLabel = state.allowMultiselect
? label
: {
...label,
touched: true,
set: false,
};
allLabels.push(unchangedLabel);
}
return allLabels;
}, []);
......
......@@ -13,10 +13,11 @@ export default () => ({
labelsFilterBasePath: '',
// UI Flags
variant: '',
allowLabelCreate: false,
allowLabelEdit: false,
allowScopedLabels: false,
dropdownOnly: false,
allowMultiselect: false,
showDropdownButton: false,
showDropdownContents: false,
showDropdownContentsCreateView: false,
......
......@@ -1032,6 +1032,16 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu {
}
.labels-select-wrapper {
&.is-standalone {
.labels-select-dropdown-contents {
max-height: 350px;
.dropdown-content {
height: 250px;
}
}
}
.labels-select-dropdown-contents {
min-height: $dropdown-min-height;
max-height: 330px;
......
......@@ -67,7 +67,7 @@ export default {
// sidebar size.
debounce(() => {
this.sidebarExpandedOnClick = true;
if (contentContainer) {
if (this.canUpdate && contentContainer) {
contentContainer
.querySelector('.js-sidebar-dropdown-toggle')
.dispatchEvent(new Event('click', { bubbles: true, cancelable: false }));
......@@ -111,12 +111,14 @@ export default {
<labels-select-vue
:allow-label-edit="canUpdate"
:allow-label-create="true"
:allow-multiselect="true"
:allow-scoped-labels="scopedLabels"
:selected-labels="labels"
:labels-select-in-progress="epicLabelsSelectInProgress"
:labels-fetch-path="labelsPath"
:labels-manage-path="labelsWebUrl"
:labels-filter-base-path="epicsWebUrl"
variant="sidebar"
class="block labels js-labels-block"
@updateSelectedLabels="handleUpdateSelectedLabels"
@onDropdownClose="handleDropdownClose"
......
......@@ -175,7 +175,7 @@ describe 'Epic show', :js do
it 'shows label create view when `Create group label` is clicked' do
page.within('.js-labels-block') do
find('button', text: 'Create group label').click
find('a', text: 'Create group label').click
expect(page).to have_selector('.js-labels-create')
end
......@@ -183,7 +183,7 @@ describe 'Epic show', :js do
it 'creates new label using create view' do
page.within('.js-labels-block') do
find('button', text: 'Create group label').click
find('a', text: 'Create group label').click
find('.dropdown-input .gl-form-input').set('Test label')
find('.suggest-colors-dropdown a', match: :first).click
......@@ -200,7 +200,7 @@ describe 'Epic show', :js do
it 'shows labels list view when `Cancel` button is clicked from create view' do
page.within('.js-labels-block') do
find('button', text: 'Create group label').click
find('a', text: 'Create group label').click
find('.js-btn-cancel-create').click
wait_for_requests
......@@ -211,7 +211,7 @@ describe 'Epic show', :js do
it 'shows labels list view when back button is clicked from create view' do
page.within('.js-labels-block') do
find('button', text: 'Create group label').click
find('a', text: 'Create group label').click
find('.js-btn-back').click
wait_for_requests
......
......@@ -33,9 +33,32 @@ describe('DropdownButton', () => {
wrapper.destroy();
});
describe('methods', () => {
describe('handleButtonClick', () => {
it('calls action `toggleDropdownContents` and stops event propagation when `state.variant` is "standalone"', () => {
const event = {
stopPropagation: jest.fn(),
};
wrapper = createComponent({
...mockConfig,
variant: 'standalone',
});
jest.spyOn(wrapper.vm, 'toggleDropdownContents');
wrapper.vm.handleButtonClick(event);
expect(wrapper.vm.toggleDropdownContents).toHaveBeenCalled();
expect(event.stopPropagation).toHaveBeenCalled();
wrapper.destroy();
});
});
});
describe('template', () => {
it('renders component container element', () => {
expect(wrapper.is('gl-deprecated-button-stub')).toBe(true);
expect(wrapper.is('gl-button-stub')).toBe(true);
});
it('renders button text element', () => {
......
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlDeprecatedButton, GlIcon, GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui';
import { GlButton, GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui';
import DropdownContentsCreateView from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue';
import labelSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store';
......@@ -127,12 +127,12 @@ describe('DropdownContentsCreateView', () => {
it('renders dropdown back button element', () => {
const backBtnEl = wrapper
.find('.dropdown-title')
.findAll(GlDeprecatedButton)
.findAll(GlButton)
.at(0);
expect(backBtnEl.exists()).toBe(true);
expect(backBtnEl.attributes('aria-label')).toBe('Go back');
expect(backBtnEl.find(GlIcon).props('name')).toBe('arrow-left');
expect(backBtnEl.props('icon')).toBe('arrow-left');
});
it('renders dropdown title element', () => {
......@@ -145,12 +145,12 @@ describe('DropdownContentsCreateView', () => {
it('renders dropdown close button element', () => {
const closeBtnEl = wrapper
.find('.dropdown-title')
.findAll(GlDeprecatedButton)
.findAll(GlButton)
.at(1);
expect(closeBtnEl.exists()).toBe(true);
expect(closeBtnEl.attributes('aria-label')).toBe('Close');
expect(closeBtnEl.find(GlIcon).props('name')).toBe('close');
expect(closeBtnEl.props('icon')).toBe('close');
});
it('renders label title input element', () => {
......@@ -192,7 +192,7 @@ describe('DropdownContentsCreateView', () => {
it('renders create button element', () => {
const createBtnEl = wrapper
.find('.dropdown-actions')
.findAll(GlDeprecatedButton)
.findAll(GlButton)
.at(0);
expect(createBtnEl.exists()).toBe(true);
......@@ -213,7 +213,7 @@ describe('DropdownContentsCreateView', () => {
it('renders cancel button element', () => {
const cancelBtnEl = wrapper
.find('.dropdown-actions')
.findAll(GlDeprecatedButton)
.findAll(GlButton)
.at(1);
expect(cancelBtnEl.exists()).toBe(true);
......
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlDeprecatedButton, GlLoadingIcon, GlIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui';
import { GlButton, GlLoadingIcon, GlIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui';
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
import DropdownContentsLabelsView from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue';
......@@ -41,13 +41,19 @@ const createComponent = (initialState = mockConfig) => {
describe('DropdownContentsLabelsView', () => {
let wrapper;
let wrapperStandalone;
beforeEach(() => {
wrapper = createComponent();
wrapperStandalone = createComponent({
...mockConfig,
variant: 'standalone',
});
});
afterEach(() => {
wrapper.destroy();
wrapperStandalone.destroy();
});
describe('computed', () => {
......@@ -165,13 +171,24 @@ describe('DropdownContentsLabelsView', () => {
});
describe('handleLabelClick', () => {
it('calls action `updateSelectedLabels` with provided `label` param', () => {
beforeEach(() => {
jest.spyOn(wrapper.vm, 'updateSelectedLabels').mockImplementation();
});
it('calls action `updateSelectedLabels` with provided `label` param', () => {
wrapper.vm.handleLabelClick(mockRegularLabel);
expect(wrapper.vm.updateSelectedLabels).toHaveBeenCalledWith([mockRegularLabel]);
});
it('calls action `toggleDropdownContents` when `state.allowMultiselect` is false', () => {
jest.spyOn(wrapper.vm, 'toggleDropdownContents');
wrapper.vm.$store.state.allowMultiselect = false;
wrapper.vm.handleLabelClick(mockRegularLabel);
expect(wrapper.vm.toggleDropdownContents).toHaveBeenCalled();
});
});
});
......@@ -198,12 +215,15 @@ describe('DropdownContentsLabelsView', () => {
expect(titleEl.text()).toBe('Assign labels');
});
it('does not render dropdown title element when `state.variant` is "standalone"', () => {
expect(wrapperStandalone.find('.dropdown-title').exists()).toBe(false);
});
it('renders dropdown close button element', () => {
const closeButtonEl = wrapper.find('.dropdown-title').find(GlDeprecatedButton);
const closeButtonEl = wrapper.find('.dropdown-title').find(GlButton);
expect(closeButtonEl.exists()).toBe(true);
expect(closeButtonEl.find(GlIcon).exists()).toBe(true);
expect(closeButtonEl.find(GlIcon).props('name')).toBe('close');
expect(closeButtonEl.props('icon')).toBe('close');
});
it('renders label search input element', () => {
......@@ -253,13 +273,36 @@ describe('DropdownContentsLabelsView', () => {
});
it('renders footer list items', () => {
const createLabelBtn = wrapper.find('.dropdown-footer').find(GlDeprecatedButton);
const manageLabelsLink = wrapper.find('.dropdown-footer').find(GlLink);
expect(createLabelBtn.exists()).toBe(true);
expect(createLabelBtn.text()).toBe('Create label');
const createLabelLink = wrapper
.find('.dropdown-footer')
.findAll(GlLink)
.at(0);
const manageLabelsLink = wrapper
.find('.dropdown-footer')
.findAll(GlLink)
.at(1);
expect(createLabelLink.exists()).toBe(true);
expect(createLabelLink.text()).toBe('Create label');
expect(manageLabelsLink.exists()).toBe(true);
expect(manageLabelsLink.text()).toBe('Manage labels');
});
it('does not render "Create label" footer link when `state.allowLabelCreate` is `false`', () => {
wrapper.vm.$store.state.allowLabelCreate = false;
return wrapper.vm.$nextTick(() => {
const createLabelLink = wrapper
.find('.dropdown-footer')
.findAll(GlLink)
.at(0);
expect(createLabelLink.text()).not.toBe('Create label');
});
});
it('does not render footer list items when `state.variant` is "standalone"', () => {
expect(wrapperStandalone.find('.dropdown-footer').exists()).toBe(false);
});
});
});
......@@ -89,6 +89,19 @@ describe('LabelsSelectRoot', () => {
expect(wrapper.attributes('class')).toContain('labels-select-wrapper position-relative');
});
it('renders component root element with CSS class `is-standalone` when `state.variant` is "standalone"', () => {
const wrapperStandalone = createComponent({
...mockConfig,
variant: 'standalone',
});
return wrapperStandalone.vm.$nextTick(() => {
expect(wrapperStandalone.classes()).toContain('is-standalone');
wrapperStandalone.destroy();
});
});
it('renders `dropdown-value-collapsed` component when `allowLabelCreate` prop is `true`', () => {
expect(wrapper.find(DropdownValueCollapsed).exists()).toBe(true);
});
......@@ -101,13 +114,16 @@ describe('LabelsSelectRoot', () => {
const wrapperDropdownValue = createComponent(mockConfig, {
default: 'None',
});
wrapperDropdownValue.vm.$store.state.showDropdownButton = false;
const valueComp = wrapperDropdownValue.find(DropdownValue);
return wrapperDropdownValue.vm.$nextTick(() => {
const valueComp = wrapperDropdownValue.find(DropdownValue);
expect(valueComp.exists()).toBe(true);
expect(valueComp.text()).toBe('None');
expect(valueComp.exists()).toBe(true);
expect(valueComp.text()).toBe('None');
wrapperDropdownValue.destroy();
wrapperDropdownValue.destroy();
});
});
it('renders `dropdown-button` component when `showDropdownButton` prop is `true`', () => {
......
......@@ -30,8 +30,10 @@ export const mockConfig = {
allowLabelEdit: true,
allowLabelCreate: true,
allowScopedLabels: true,
allowMultiselect: true,
labelsListTitle: 'Assign labels',
labelsCreateTitle: 'Create label',
variant: 'sidebar',
dropdownOnly: false,
selectedLabels: [mockRegularLabel, mockScopedLabel],
labelsSelectInProgress: false,
......
......@@ -5,19 +5,25 @@ describe('LabelsSelect Getters', () => {
it('returns string "Label" when state.labels has no selected labels', () => {
const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }];
expect(getters.dropdownButtonText({ labels })).toBe('Label');
expect(getters.dropdownButtonText({ labels }, { isDropdownVariantSidebar: true })).toBe(
'Label',
);
});
it('returns label title when state.labels has only 1 label', () => {
const labels = [{ id: 1, title: 'Foobar', set: true }];
expect(getters.dropdownButtonText({ labels })).toBe('Foobar');
expect(getters.dropdownButtonText({ labels }, { isDropdownVariantSidebar: true })).toBe(
'Foobar',
);
});
it('returns first label title and remaining labels count when state.labels has more than 1 label', () => {
const labels = [{ id: 1, title: 'Foo', set: true }, { id: 2, title: 'Bar', set: true }];
expect(getters.dropdownButtonText({ labels })).toBe('Foo +1 more');
expect(getters.dropdownButtonText({ labels }, { isDropdownVariantSidebar: true })).toBe(
'Foo +1 more',
);
});
});
......@@ -28,4 +34,16 @@ describe('LabelsSelect Getters', () => {
expect(getters.selectedLabelsList({ selectedLabels })).toEqual([1, 2, 3, 4]);
});
});
describe('isDropdownVariantSidebar', () => {
it('returns `true` when `state.variant` is "sidebar"', () => {
expect(getters.isDropdownVariantSidebar({ variant: 'sidebar' })).toBe(true);
});
});
describe('isDropdownVariantStandalone', () => {
it('returns `true` when `state.variant` is "standalone"', () => {
expect(getters.isDropdownVariantStandalone({ variant: 'standalone' })).toBe(true);
});
});
});
......@@ -29,6 +29,7 @@ describe('LabelsSelect Mutations', () => {
const state = {
dropdownOnly: false,
showDropdownButton: false,
variant: 'sidebar',
};
mutations[types.TOGGLE_DROPDOWN_CONTENTS](state);
......@@ -154,10 +155,27 @@ describe('LabelsSelect Mutations', () => {
describe(`${types.UPDATE_SELECTED_LABELS}`, () => {
const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }];
it('updates `state.labels` to include `touched` and `set` props based on provided `labels` param', () => {
it('updates `state.labels` to include `touched` and `set` props based on provided `labels` param when `state.allowMultiselect` is `true`', () => {
const updatedLabelIds = [2, 4];
const state = {
labels,
allowMultiselect: true,
};
mutations[types.UPDATE_SELECTED_LABELS](state, { labels });
state.labels.forEach(label => {
if (updatedLabelIds.includes(label.id)) {
expect(label.touched).toBe(true);
expect(label.set).toBe(true);
}
});
});
it('updates `state.labels` to include `touched` and `set` props based on provided `labels` param when `state.allowMultiselect` is `false`', () => {
const updatedLabelIds = [2];
const state = {
labels,
allowMultiselect: false,
};
mutations[types.UPDATE_SELECTED_LABELS](state, { labels });
......
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