summaryrefslogtreecommitdiff
path: root/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js')
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js434
1 files changed, 145 insertions, 289 deletions
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js
index 51301387c99..8bd944a3d54 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js
@@ -1,357 +1,213 @@
-import { GlIntersectionObserver, GlLoadingIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui';
+import { GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
-import Vuex from 'vuex';
-import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
+import { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import createFlash from '~/flash';
+import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
+import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_widget/constants';
import DropdownContentsLabelsView from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue';
+import projectLabelsQuery from '~/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql';
import LabelItem from '~/vue_shared/components/sidebar/labels_select_widget/label_item.vue';
+import { mockConfig, labelsQueryResponse } from './mock_data';
-import * as actions from '~/vue_shared/components/sidebar/labels_select_widget/store/actions';
-import * as getters from '~/vue_shared/components/sidebar/labels_select_widget/store/getters';
-import mutations from '~/vue_shared/components/sidebar/labels_select_widget/store/mutations';
-import defaultState from '~/vue_shared/components/sidebar/labels_select_widget/store/state';
-
-import { mockConfig, mockLabels, mockRegularLabel } from './mock_data';
+jest.mock('~/flash');
const localVue = createLocalVue();
-localVue.use(Vuex);
+localVue.use(VueApollo);
+
+const selectedLabels = [
+ {
+ id: 28,
+ title: 'Bug',
+ description: 'Label for bugs',
+ color: '#FF0000',
+ textColor: '#FFFFFF',
+ },
+];
describe('DropdownContentsLabelsView', () => {
let wrapper;
- const createComponent = (initialState = mockConfig) => {
- const store = new Vuex.Store({
- getters,
- mutations,
- state: {
- ...defaultState(),
- footerCreateLabelTitle: 'Create label',
- footerManageLabelTitle: 'Manage labels',
- },
- actions: {
- ...actions,
- fetchLabels: jest.fn(),
- },
- });
+ const successfulQueryHandler = jest.fn().mockResolvedValue(labelsQueryResponse);
- store.dispatch('setInitialState', initialState);
- store.dispatch('receiveLabelsSuccess', mockLabels);
+ const createComponent = ({
+ initialState = mockConfig,
+ queryHandler = successfulQueryHandler,
+ injected = {},
+ } = {}) => {
+ const mockApollo = createMockApollo([[projectLabelsQuery, queryHandler]]);
wrapper = shallowMount(DropdownContentsLabelsView, {
localVue,
- store,
+ apolloProvider: mockApollo,
+ provide: {
+ projectPath: 'test',
+ iid: 1,
+ allowLabelCreate: true,
+ labelsManagePath: '/gitlab-org/my-project/-/labels',
+ variant: DropdownVariant.Sidebar,
+ ...injected,
+ },
+ propsData: {
+ ...initialState,
+ selectedLabels,
+ },
+ stubs: {
+ GlSearchBoxByType,
+ },
});
};
- beforeEach(() => {
- createComponent();
- });
-
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
- const findDropdownContent = () => wrapper.find('[data-testid="dropdown-content"]');
+ const findSearchInput = () => wrapper.findComponent(GlSearchBoxByType);
+ const findLabels = () => wrapper.findAllComponents(LabelItem);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+
+ const findLabelsList = () => wrapper.find('[data-testid="labels-list"]');
+ const findDropdownWrapper = () => wrapper.find('[data-testid="dropdown-wrapper"]');
const findDropdownFooter = () => wrapper.find('[data-testid="dropdown-footer"]');
- const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
-
- describe('computed', () => {
- describe('visibleLabels', () => {
- it('returns matching labels filtered with `searchKey`', () => {
- wrapper.setData({
- searchKey: 'bug',
- });
-
- expect(wrapper.vm.visibleLabels.length).toBe(1);
- expect(wrapper.vm.visibleLabels[0].title).toBe('Bug');
- });
-
- it('returns matching labels with fuzzy filtering', () => {
- wrapper.setData({
- searchKey: 'bg',
- });
-
- expect(wrapper.vm.visibleLabels.length).toBe(2);
- expect(wrapper.vm.visibleLabels[0].title).toBe('Bug');
- expect(wrapper.vm.visibleLabels[1].title).toBe('Boog');
- });
-
- it('returns all labels when `searchKey` is empty', () => {
- wrapper.setData({
- searchKey: '',
- });
-
- expect(wrapper.vm.visibleLabels.length).toBe(mockLabels.length);
- });
- });
+ const findNoResultsMessage = () => wrapper.find('[data-testid="no-results"]');
+ const findCreateLabelButton = () => wrapper.find('[data-testid="create-label-button"]');
- describe('showNoMatchingResultsMessage', () => {
- it.each`
- searchKey | labels | labelsDescription | returnValue
- ${''} | ${[]} | ${'empty'} | ${false}
- ${'bug'} | ${[]} | ${'empty'} | ${true}
- ${''} | ${mockLabels} | ${'not empty'} | ${false}
- ${'bug'} | ${mockLabels} | ${'not empty'} | ${false}
- `(
- 'returns $returnValue when searchKey is "$searchKey" and visibleLabels is $labelsDescription',
- async ({ searchKey, labels, returnValue }) => {
- wrapper.setData({
- searchKey,
- });
-
- wrapper.vm.$store.dispatch('receiveLabelsSuccess', labels);
-
- await wrapper.vm.$nextTick();
-
- expect(wrapper.vm.showNoMatchingResultsMessage).toBe(returnValue);
- },
- );
+ describe('when loading labels', () => {
+ it('renders disabled search input field', async () => {
+ createComponent();
+ expect(findSearchInput().props('disabled')).toBe(true);
});
- });
-
- describe('methods', () => {
- describe('isLabelSelected', () => {
- it('returns true when provided `label` param is one of the selected labels', () => {
- expect(wrapper.vm.isLabelSelected(mockRegularLabel)).toBe(true);
- });
- it('returns false when provided `label` param is not one of the selected labels', () => {
- expect(wrapper.vm.isLabelSelected(mockLabels[2])).toBe(false);
- });
+ it('renders loading icon', async () => {
+ createComponent();
+ expect(findLoadingIcon().exists()).toBe(true);
});
- describe('handleComponentAppear', () => {
- it('calls `focusInput` on searchInput field', async () => {
- wrapper.vm.$refs.searchInput.focusInput = jest.fn();
-
- await wrapper.vm.handleComponentAppear();
-
- expect(wrapper.vm.$refs.searchInput.focusInput).toHaveBeenCalled();
- });
+ it('does not render labels list', async () => {
+ createComponent();
+ expect(findLabelsList().exists()).toBe(false);
});
+ });
- describe('handleComponentDisappear', () => {
- it('calls action `receiveLabelsSuccess` with empty array', () => {
- jest.spyOn(wrapper.vm, 'receiveLabelsSuccess');
-
- wrapper.vm.handleComponentDisappear();
-
- expect(wrapper.vm.receiveLabelsSuccess).toHaveBeenCalledWith([]);
- });
+ describe('when labels are loaded', () => {
+ beforeEach(async () => {
+ createComponent();
+ await waitForPromises();
});
- 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();
- });
+ it('renders enabled search input field', async () => {
+ expect(findSearchInput().props('disabled')).toBe(false);
});
- describe('handleKeyDown', () => {
- it('decreases `currentHighlightItem` value by 1 when Up arrow key is pressed', () => {
- wrapper.setData({
- currentHighlightItem: 1,
- });
-
- wrapper.vm.handleKeyDown({
- keyCode: UP_KEY_CODE,
- });
-
- expect(wrapper.vm.currentHighlightItem).toBe(0);
- });
-
- it('increases `currentHighlightItem` value by 1 when Down arrow key is pressed', () => {
- wrapper.setData({
- currentHighlightItem: 1,
- });
-
- wrapper.vm.handleKeyDown({
- keyCode: DOWN_KEY_CODE,
- });
-
- expect(wrapper.vm.currentHighlightItem).toBe(2);
- });
-
- it('resets the search text when the Enter key is pressed', () => {
- wrapper.setData({
- currentHighlightItem: 1,
- searchKey: 'bug',
- });
-
- wrapper.vm.handleKeyDown({
- keyCode: ENTER_KEY_CODE,
- });
-
- expect(wrapper.vm.searchKey).toBe('');
- });
-
- it('calls action `updateSelectedLabels` with currently highlighted label when Enter key is pressed', () => {
- jest.spyOn(wrapper.vm, 'updateSelectedLabels').mockImplementation();
- wrapper.setData({
- currentHighlightItem: 1,
- });
-
- wrapper.vm.handleKeyDown({
- keyCode: ENTER_KEY_CODE,
- });
-
- expect(wrapper.vm.updateSelectedLabels).toHaveBeenCalledWith([
- {
- ...mockLabels[1],
- set: true,
- },
- ]);
- });
-
- it('calls action `toggleDropdownContents` when Esc key is pressed', () => {
- jest.spyOn(wrapper.vm, 'toggleDropdownContents').mockImplementation();
- wrapper.setData({
- currentHighlightItem: 1,
- });
-
- wrapper.vm.handleKeyDown({
- keyCode: ESC_KEY_CODE,
- });
-
- expect(wrapper.vm.toggleDropdownContents).toHaveBeenCalled();
- });
-
- it('calls action `scrollIntoViewIfNeeded` in next tick when any key is pressed', () => {
- jest.spyOn(wrapper.vm, 'scrollIntoViewIfNeeded').mockImplementation();
- wrapper.setData({
- currentHighlightItem: 1,
- });
-
- wrapper.vm.handleKeyDown({
- keyCode: DOWN_KEY_CODE,
- });
-
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.scrollIntoViewIfNeeded).toHaveBeenCalled();
- });
- });
+ it('does not render loading icon', async () => {
+ expect(findLoadingIcon().exists()).toBe(false);
});
- describe('handleLabelClick', () => {
- 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('renders labels list', async () => {
+ expect(findLabelsList().exists()).toBe(true);
+ expect(findLabels()).toHaveLength(2);
+ });
- it('calls action `toggleDropdownContents` when `state.allowMultiselect` is false', () => {
- jest.spyOn(wrapper.vm, 'toggleDropdownContents');
- wrapper.vm.$store.state.allowMultiselect = false;
+ it('changes highlighted label correctly on pressing down button', async () => {
+ expect(findLabels().at(0).attributes('highlight')).toBeUndefined();
- wrapper.vm.handleLabelClick(mockRegularLabel);
+ await findDropdownWrapper().trigger('keydown.down');
+ expect(findLabels().at(0).attributes('highlight')).toBe('true');
- expect(wrapper.vm.toggleDropdownContents).toHaveBeenCalled();
- });
+ await findDropdownWrapper().trigger('keydown.down');
+ expect(findLabels().at(1).attributes('highlight')).toBe('true');
+ expect(findLabels().at(0).attributes('highlight')).toBeUndefined();
});
- });
- describe('template', () => {
- it('renders gl-intersection-observer as component root', () => {
- expect(wrapper.find(GlIntersectionObserver).exists()).toBe(true);
- });
+ it('changes highlighted label correctly on pressing up button', async () => {
+ await findDropdownWrapper().trigger('keydown.down');
+ await findDropdownWrapper().trigger('keydown.down');
+ expect(findLabels().at(1).attributes('highlight')).toBe('true');
- it('renders gl-loading-icon component when `labelsFetchInProgress` prop is true', () => {
- wrapper.vm.$store.dispatch('requestLabels');
+ await findDropdownWrapper().trigger('keydown.up');
+ expect(findLabels().at(0).attributes('highlight')).toBe('true');
+ });
- return wrapper.vm.$nextTick(() => {
- const loadingIconEl = findLoadingIcon();
+ it('changes label selected state when Enter is pressed', async () => {
+ expect(findLabels().at(0).attributes('islabelset')).toBeUndefined();
+ await findDropdownWrapper().trigger('keydown.down');
+ await findDropdownWrapper().trigger('keydown.enter');
- expect(loadingIconEl.exists()).toBe(true);
- expect(loadingIconEl.attributes('class')).toContain('labels-fetch-loading');
- });
+ expect(findLabels().at(0).attributes('islabelset')).toBe('true');
});
- it('renders label search input element', () => {
- const searchInputEl = wrapper.find(GlSearchBoxByType);
+ it('emits `closeDropdown event` when Esc button is pressed', () => {
+ findDropdownWrapper().trigger('keydown.esc');
- expect(searchInputEl.exists()).toBe(true);
+ expect(wrapper.emitted('closeDropdown')).toEqual([[selectedLabels]]);
});
+ });
- it('renders label elements for all labels', () => {
- expect(wrapper.findAll(LabelItem)).toHaveLength(mockLabels.length);
+ it('when search returns 0 results', async () => {
+ createComponent({
+ queryHandler: jest.fn().mockResolvedValue({
+ data: {
+ workspace: {
+ labels: {
+ nodes: [],
+ },
+ },
+ },
+ }),
});
+ findSearchInput().vm.$emit('input', '123');
+ await waitForPromises();
+ await nextTick();
- it('renders label element with `highlight` set to true when value of `currentHighlightItem` is more than -1', () => {
- wrapper.setData({
- currentHighlightItem: 0,
- });
+ expect(findNoResultsMessage().isVisible()).toBe(true);
+ });
- return wrapper.vm.$nextTick(() => {
- const labelItemEl = findDropdownContent().find(LabelItem);
+ it('calls `createFlash` when fetching labels failed', async () => {
+ createComponent({ queryHandler: jest.fn().mockRejectedValue('Houston, we have a problem!') });
+ jest.advanceTimersByTime(DEFAULT_DEBOUNCE_AND_THROTTLE_MS);
+ await waitForPromises();
+ expect(createFlash).toHaveBeenCalled();
+ });
- expect(labelItemEl.attributes('highlight')).toBe('true');
- });
- });
+ it('does not render footer on standalone dropdown', () => {
+ createComponent({ injected: { variant: DropdownVariant.Standalone } });
- it('renders element containing "No matching results" when `searchKey` does not match with any label', () => {
- wrapper.setData({
- searchKey: 'abc',
- });
+ expect(findDropdownFooter().exists()).toBe(false);
+ });
- return wrapper.vm.$nextTick(() => {
- const noMatchEl = findDropdownContent().find('li');
+ it('renders footer on sidebar dropdown', () => {
+ createComponent();
- expect(noMatchEl.isVisible()).toBe(true);
- expect(noMatchEl.text()).toContain('No matching results');
- });
- });
+ expect(findDropdownFooter().exists()).toBe(true);
+ });
- it('renders empty content while loading', () => {
- wrapper.vm.$store.state.labelsFetchInProgress = true;
+ it('renders footer on embedded dropdown', () => {
+ createComponent({ injected: { variant: DropdownVariant.Embedded } });
- return wrapper.vm.$nextTick(() => {
- const dropdownContent = findDropdownContent();
- const loadingIcon = findLoadingIcon();
+ expect(findDropdownFooter().exists()).toBe(true);
+ });
- expect(dropdownContent.exists()).toBe(true);
- expect(dropdownContent.isVisible()).toBe(true);
- expect(loadingIcon.exists()).toBe(true);
- expect(loadingIcon.isVisible()).toBe(true);
- });
- });
+ it('does not render create label button if `allowLabelCreate` is false', () => {
+ createComponent({ injected: { allowLabelCreate: false } });
- it('renders footer list items', () => {
- const footerLinks = findDropdownFooter().findAll(GlLink);
- const createLabelLink = footerLinks.at(0);
- const manageLabelsLink = footerLinks.at(1);
+ expect(findCreateLabelButton().exists()).toBe(false);
+ });
- expect(createLabelLink.exists()).toBe(true);
- expect(createLabelLink.text()).toBe('Create label');
- expect(manageLabelsLink.exists()).toBe(true);
- expect(manageLabelsLink.text()).toBe('Manage labels');
+ describe('when `allowLabelCreate` is true', () => {
+ beforeEach(() => {
+ createComponent();
});
- 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 = findDropdownFooter().findAll(GlLink).at(0);
-
- expect(createLabelLink.text()).not.toBe('Create label');
- });
+ it('renders create label button', () => {
+ expect(findCreateLabelButton().exists()).toBe(true);
});
- it('does not render footer list items when `state.variant` is "standalone"', () => {
- createComponent({ ...mockConfig, variant: 'standalone' });
- expect(findDropdownFooter().exists()).toBe(false);
- });
+ it('emits `toggleDropdownContentsCreateView` event on create label button click', () => {
+ findCreateLabelButton().vm.$emit('click');
- it('renders footer list items when `state.variant` is "embedded"', () => {
- expect(findDropdownFooter().exists()).toBe(true);
+ expect(wrapper.emitted('toggleDropdownContentsCreateView')).toEqual([[]]);
});
});
});