diff options
Diffstat (limited to 'spec/frontend/vue_shared/components/sidebar/labels_select_vue')
9 files changed, 247 insertions, 52 deletions
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js index e2d31a41e82..214eb239432 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js @@ -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', () => { diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js index d7ca7ce30a9..04320a72be6 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js @@ -1,7 +1,7 @@ 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); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js index 3e6dbdb7ecb..74c769f86a3 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js @@ -1,9 +1,10 @@ import Vuex from 'vuex'; import { shallowMount, createLocalVue } from '@vue/test-utils'; -import { GlDeprecatedButton, GlLoadingIcon, GlIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui'; +import { 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 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 defaultState from '~/vue_shared/components/sidebar/labels_select_vue/store/state'; import mutations from '~/vue_shared/components/sidebar/labels_select_vue/store/mutations'; @@ -41,13 +42,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', () => { @@ -72,16 +79,6 @@ describe('DropdownContentsLabelsView', () => { }); describe('methods', () => { - describe('getDropdownLabelBoxStyle', () => { - it('returns an object containing `backgroundColor` based on provided `label` param', () => { - expect(wrapper.vm.getDropdownLabelBoxStyle(mockRegularLabel)).toEqual( - expect.objectContaining({ - backgroundColor: mockRegularLabel.color, - }), - ); - }); - }); - describe('isLabelSelected', () => { it('returns true when provided `label` param is one of the selected labels', () => { expect(wrapper.vm.isLabelSelected(mockRegularLabel)).toBe(true); @@ -165,13 +162,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 +206,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', () => { @@ -214,16 +225,7 @@ describe('DropdownContentsLabelsView', () => { }); it('renders label elements for all labels', () => { - const labelsEl = wrapper.findAll('.dropdown-content li'); - const labelItemEl = labelsEl.at(0).find(GlLink); - - expect(labelsEl.length).toBe(mockLabels.length); - expect(labelItemEl.exists()).toBe(true); - expect(labelItemEl.find(GlIcon).props('name')).toBe('mobile-issue-close'); - expect(labelItemEl.find('.dropdown-label-box').attributes('style')).toBe( - 'background-color: rgb(186, 218, 85);', - ); - expect(labelItemEl.find(GlLink).text()).toContain(mockLabels[0].title); + expect(wrapper.findAll(LabelItem)).toHaveLength(mockLabels.length); }); it('renders label element with "is-focused" when value of `currentHighlightItem` is more than -1', () => { @@ -233,9 +235,9 @@ describe('DropdownContentsLabelsView', () => { return wrapper.vm.$nextTick(() => { const labelsEl = wrapper.findAll('.dropdown-content li'); - const labelItemEl = labelsEl.at(0).find(GlLink); + const labelItemEl = labelsEl.at(0).find(LabelItem); - expect(labelItemEl.attributes('class')).toContain('is-focused'); + expect(labelItemEl.props('highlight')).toBe(true); }); }); @@ -247,19 +249,42 @@ describe('DropdownContentsLabelsView', () => { return wrapper.vm.$nextTick(() => { const noMatchEl = wrapper.find('.dropdown-content li'); - expect(noMatchEl.exists()).toBe(true); + expect(noMatchEl.isVisible()).toBe(true); expect(noMatchEl.text()).toContain('No matching results'); }); }); 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); + }); }); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js new file mode 100644 index 00000000000..401d208da5c --- /dev/null +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js @@ -0,0 +1,111 @@ +import { shallowMount } from '@vue/test-utils'; + +import { GlIcon, GlLink } from '@gitlab/ui'; +import LabelItem from '~/vue_shared/components/sidebar/labels_select_vue/label_item.vue'; +import { mockRegularLabel } from './mock_data'; + +const createComponent = ({ label = mockRegularLabel, highlight = true } = {}) => + shallowMount(LabelItem, { + propsData: { + label, + highlight, + }, + }); + +describe('LabelItem', () => { + let wrapper; + + beforeEach(() => { + wrapper = createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('computed', () => { + describe('labelBoxStyle', () => { + it('returns an object containing `backgroundColor` based on `label` prop', () => { + expect(wrapper.vm.labelBoxStyle).toEqual( + expect.objectContaining({ + backgroundColor: mockRegularLabel.color, + }), + ); + }); + }); + }); + + 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([mockRegularLabel]); + }); + }); + }); + + describe('template', () => { + it('renders gl-link component', () => { + expect(wrapper.find(GlLink).exists()).toBe(true); + }); + + it('renders gl-link component with class `is-focused` when `highlight` prop is true', () => { + wrapper.setProps({ + highlight: true, + }); + + return wrapper.vm.$nextTick(() => { + expect(wrapper.find(GlLink).classes()).toContain('is-focused'); + }); + }); + + it('renders visible gl-icon component when `isSet` prop is true', () => { + wrapper.setData({ + isSet: true, + }); + + return wrapper.vm.$nextTick(() => { + const iconEl = wrapper.find(GlIcon); + + expect(iconEl.isVisible()).toBe(true); + expect(iconEl.props('name')).toBe('mobile-issue-close'); + }); + }); + + it('renders visible span element as placeholder instead of gl-icon when `isSet` prop is false', () => { + wrapper.setData({ + isSet: false, + }); + + return wrapper.vm.$nextTick(() => { + const placeholderEl = wrapper.find('[data-testid="no-icon"]'); + + expect(placeholderEl.isVisible()).toBe(true); + }); + }); + + it('renders label color element', () => { + const colorEl = wrapper.find('[data-testid="label-color-box"]'); + + expect(colorEl.exists()).toBe(true); + expect(colorEl.attributes('style')).toBe('background-color: rgb(186, 218, 85);'); + }); + + it('renders label title', () => { + expect(wrapper.text()).toContain(mockRegularLabel.title); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js index 126fd5438c4..ee4e9090e5d 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js @@ -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`', () => { diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/mock_data.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/mock_data.js index a863cddbaee..e1008d13fc2 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/mock_data.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/mock_data.js @@ -30,15 +30,16 @@ 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, labelsFetchPath: '/gitlab-org/my-project/-/labels.json', labelsManagePath: '/gitlab-org/my-project/-/labels', labelsFilterBasePath: '/gitlab-org/my-project/issues', - scopedLabelsDocumentationPath: '/help/user/project/labels.md#scoped-labels-premium', }; export const mockSuggestedColors = { diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/actions_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/actions_spec.js index 6e2363ba96f..072d8fe2fe2 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/actions_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/actions_spec.js @@ -15,7 +15,7 @@ describe('LabelsSelect Actions', () => { }; beforeEach(() => { - state = Object.assign({}, defaultState()); + state = { ...defaultState() }; }); describe('setInitialState', () => { diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js index bfceaa0828b..b866117efcf 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js @@ -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); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js index f6ca98fcc71..8081806e314 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js @@ -29,6 +29,7 @@ describe('LabelsSelect Mutations', () => { const state = { dropdownOnly: false, showDropdownButton: false, + variant: 'sidebar', }; mutations[types.TOGGLE_DROPDOWN_CONTENTS](state); @@ -155,11 +156,11 @@ describe('LabelsSelect Mutations', () => { 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', () => { - const updatedLabelIds = [2, 4]; + const updatedLabelIds = [2]; const state = { labels, }; - mutations[types.UPDATE_SELECTED_LABELS](state, { labels }); + mutations[types.UPDATE_SELECTED_LABELS](state, { labels: [{ id: 2 }] }); state.labels.forEach(label => { if (updatedLabelIds.includes(label.id)) { |