697 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			697 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| import { GlLoadingIcon, GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
 | |
| import Vue, { nextTick } from 'vue';
 | |
| import axios from 'axios';
 | |
| import MockAdapter from 'axios-mock-adapter';
 | |
| import { merge, last } from 'lodash';
 | |
| import Vuex from 'vuex';
 | |
| import commit from 'test_fixtures/api/commits/commit.json';
 | |
| import branches from 'test_fixtures/api/branches/branches.json';
 | |
| import tags from 'test_fixtures/api/tags/tags.json';
 | |
| import { mountExtended } from 'helpers/vue_test_utils_helper';
 | |
| import { trimText } from 'helpers/text_helper';
 | |
| import {
 | |
|   HTTP_STATUS_INTERNAL_SERVER_ERROR,
 | |
|   HTTP_STATUS_NOT_FOUND,
 | |
|   HTTP_STATUS_OK,
 | |
| } from '~/lib/utils/http_status';
 | |
| import { sprintf } from '~/locale';
 | |
| import RefSelector from '~/ref/components/ref_selector.vue';
 | |
| import {
 | |
|   X_TOTAL_HEADER,
 | |
|   DEFAULT_I18N,
 | |
|   REF_TYPE_BRANCHES,
 | |
|   REF_TYPE_TAGS,
 | |
|   REF_TYPE_COMMITS,
 | |
|   BRANCH_REF_TYPE,
 | |
|   TAG_REF_TYPE,
 | |
| } from '~/ref/constants';
 | |
| import createStore from '~/ref/stores/';
 | |
| 
 | |
| Vue.use(Vuex);
 | |
| 
 | |
| describe('Ref selector component', () => {
 | |
|   const fixtures = { branches, tags, commit };
 | |
| 
 | |
|   const projectId = '8';
 | |
| 
 | |
|   let wrapper;
 | |
|   let branchesApiCallSpy;
 | |
|   let tagsApiCallSpy;
 | |
|   let commitApiCallSpy;
 | |
|   let requestSpies;
 | |
| 
 | |
|   const createComponent = (mountOverrides = {}, propsData = {}) => {
 | |
|     wrapper = mountExtended(
 | |
|       RefSelector,
 | |
|       merge(
 | |
|         {
 | |
|           propsData: {
 | |
|             projectId,
 | |
|             value: '',
 | |
|             ...propsData,
 | |
|           },
 | |
|           listeners: {
 | |
|             // simulate a parent component v-model binding
 | |
|             input: (selectedRef) => {
 | |
|               wrapper.setProps({ value: selectedRef });
 | |
|             },
 | |
|           },
 | |
|           store: createStore(),
 | |
|         },
 | |
|         mountOverrides,
 | |
|       ),
 | |
|     );
 | |
|   };
 | |
| 
 | |
|   beforeEach(() => {
 | |
|     const mock = new MockAdapter(axios);
 | |
|     gon.api_version = 'v4';
 | |
| 
 | |
|     branchesApiCallSpy = jest
 | |
|       .fn()
 | |
|       .mockReturnValue([HTTP_STATUS_OK, fixtures.branches, { [X_TOTAL_HEADER]: '123' }]);
 | |
|     tagsApiCallSpy = jest
 | |
|       .fn()
 | |
|       .mockReturnValue([HTTP_STATUS_OK, fixtures.tags, { [X_TOTAL_HEADER]: '456' }]);
 | |
|     commitApiCallSpy = jest.fn().mockReturnValue([HTTP_STATUS_OK, fixtures.commit]);
 | |
|     requestSpies = { branchesApiCallSpy, tagsApiCallSpy, commitApiCallSpy };
 | |
| 
 | |
|     mock
 | |
|       .onGet(`/api/v4/projects/${projectId}/repository/branches`)
 | |
|       .reply((config) => branchesApiCallSpy(config));
 | |
|     mock
 | |
|       .onGet(`/api/v4/projects/${projectId}/repository/tags`)
 | |
|       .reply((config) => tagsApiCallSpy(config));
 | |
|     mock
 | |
|       .onGet(new RegExp(`/api/v4/projects/${projectId}/repository/commits/.*`))
 | |
|       .reply((config) => commitApiCallSpy(config));
 | |
|   });
 | |
| 
 | |
|   //
 | |
|   // Finders
 | |
|   //
 | |
|   const findListbox = () => wrapper.findComponent(GlCollapsibleListbox);
 | |
| 
 | |
|   const findButtonToggle = () => wrapper.findByTestId('base-dropdown-toggle');
 | |
| 
 | |
|   const findNoResults = () => wrapper.findByTestId('listbox-no-results-text');
 | |
| 
 | |
|   const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
 | |
| 
 | |
|   const findListBoxSection = (section) => {
 | |
|     const foundSections = wrapper
 | |
|       .findAll('[role="group"]')
 | |
|       .filter((ul) => ul.text().includes(section));
 | |
|     return foundSections.length > 0 ? foundSections.at(0) : foundSections;
 | |
|   };
 | |
| 
 | |
|   const findErrorListWrapper = () => wrapper.findByTestId('red-selector-error-list');
 | |
| 
 | |
|   const findBranchesSection = () => findListBoxSection('Branches');
 | |
|   const findBranchDropdownItems = () => wrapper.findAllComponents(GlListboxItem);
 | |
| 
 | |
|   const findTagsSection = () => findListBoxSection('Tags');
 | |
| 
 | |
|   const findCommitsSection = () => findListBoxSection('Commits');
 | |
| 
 | |
|   const findHiddenInputField = () => wrapper.findByTestId('selected-ref-form-field');
 | |
| 
 | |
|   //
 | |
|   // Expecters
 | |
|   //
 | |
|   const sectionContainsErrorMessage = (message) => {
 | |
|     const errorSection = findErrorListWrapper();
 | |
| 
 | |
|     return errorSection ? errorSection.text().includes(message) : false;
 | |
|   };
 | |
| 
 | |
|   //
 | |
|   // Convenience methods
 | |
|   //
 | |
|   const updateQuery = (newQuery) => {
 | |
|     findListbox().vm.$emit('search', newQuery);
 | |
|   };
 | |
| 
 | |
|   const selectFirstBranch = async () => {
 | |
|     findListbox().vm.$emit('select', fixtures.branches[0].name);
 | |
|     await nextTick();
 | |
|   };
 | |
| 
 | |
|   const selectFirstTag = async () => {
 | |
|     findListbox().vm.$emit('select', fixtures.tags[0].name);
 | |
|     await nextTick();
 | |
|   };
 | |
| 
 | |
|   const selectFirstCommit = async () => {
 | |
|     findListbox().vm.$emit('select', fixtures.commit.id);
 | |
|     await nextTick();
 | |
|   };
 | |
| 
 | |
|   const waitForRequests = ({ andClearMocks } = { andClearMocks: false }) =>
 | |
|     axios.waitForAll().then(() => {
 | |
|       if (andClearMocks) {
 | |
|         branchesApiCallSpy.mockClear();
 | |
|         tagsApiCallSpy.mockClear();
 | |
|         commitApiCallSpy.mockClear();
 | |
|       }
 | |
|     });
 | |
| 
 | |
|   describe('initialization behavior', () => {
 | |
|     it('initializes the dropdown with branches and tags when mounted', () => {
 | |
|       createComponent();
 | |
| 
 | |
|       return waitForRequests().then(() => {
 | |
|         expect(branchesApiCallSpy).toHaveBeenCalledTimes(1);
 | |
|         expect(tagsApiCallSpy).toHaveBeenCalledTimes(1);
 | |
|         expect(commitApiCallSpy).not.toHaveBeenCalled();
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('shows a spinner while network requests are in progress', () => {
 | |
|       createComponent();
 | |
| 
 | |
|       expect(findLoadingIcon().exists()).toBe(true);
 | |
| 
 | |
|       return waitForRequests().then(() => {
 | |
|         expect(findLoadingIcon().exists()).toBe(false);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe('when name property is provided', () => {
 | |
|       it('renders an form input hidden field', () => {
 | |
|         const name = 'default_tag';
 | |
| 
 | |
|         createComponent({ propsData: { name } });
 | |
| 
 | |
|         expect(findHiddenInputField().attributes().name).toBe(name);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe('when name property is not provided', () => {
 | |
|       it('renders an form input hidden field', () => {
 | |
|         createComponent();
 | |
| 
 | |
|         expect(findHiddenInputField().exists()).toBe(false);
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('post-initialization behavior', () => {
 | |
|     describe('when the parent component provides an `id` binding', () => {
 | |
|       const id = 'git-ref';
 | |
| 
 | |
|       beforeEach(() => {
 | |
|         createComponent({ attrs: { id } });
 | |
| 
 | |
|         return waitForRequests();
 | |
|       });
 | |
| 
 | |
|       it('adds the provided ID to the GlDropdown instance', () => {
 | |
|         expect(findListbox().attributes().id).toBe(id);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe('when a ref is pre-selected', () => {
 | |
|       const preselectedRef = fixtures.branches[0].name;
 | |
| 
 | |
|       beforeEach(() => {
 | |
|         createComponent({ propsData: { value: preselectedRef, name: 'selectedRef' } });
 | |
| 
 | |
|         return waitForRequests();
 | |
|       });
 | |
| 
 | |
|       it('renders the pre-selected ref name', () => {
 | |
|         expect(findButtonToggle().text()).toBe(preselectedRef);
 | |
|       });
 | |
| 
 | |
|       it('binds hidden input field to the pre-selected ref', () => {
 | |
|         expect(findHiddenInputField().attributes().value).toBe(preselectedRef);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe('when the selected ref is updated by the parent component', () => {
 | |
|       const updatedRef = fixtures.branches[0].name;
 | |
| 
 | |
|       beforeEach(() => {
 | |
|         createComponent();
 | |
| 
 | |
|         return waitForRequests();
 | |
|       });
 | |
| 
 | |
|       it('renders the updated ref name', async () => {
 | |
|         wrapper.setProps({ value: updatedRef });
 | |
| 
 | |
|         await nextTick();
 | |
|         expect(findButtonToggle().text()).toBe(updatedRef);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe('when the search query is updated', () => {
 | |
|       beforeEach(() => {
 | |
|         createComponent();
 | |
| 
 | |
|         return waitForRequests({ andClearMocks: true });
 | |
|       });
 | |
| 
 | |
|       it('requeries the endpoints when the search query is updated', () => {
 | |
|         updateQuery('v1.2.3');
 | |
| 
 | |
|         return waitForRequests().then(() => {
 | |
|           expect(branchesApiCallSpy).toHaveBeenCalledTimes(1);
 | |
|           expect(tagsApiCallSpy).toHaveBeenCalledTimes(1);
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       it("does not make a call to the commit endpoint if the query doesn't look like a SHA", () => {
 | |
|         updateQuery('not a sha');
 | |
| 
 | |
|         return waitForRequests().then(() => {
 | |
|           expect(commitApiCallSpy).not.toHaveBeenCalled();
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       it('searches for a commit if the query could potentially be a SHA', () => {
 | |
|         updateQuery('abcdef');
 | |
| 
 | |
|         return waitForRequests().then(() => {
 | |
|           expect(commitApiCallSpy).toHaveBeenCalled();
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe('when no results are found', () => {
 | |
|       beforeEach(() => {
 | |
|         branchesApiCallSpy = jest
 | |
|           .fn()
 | |
|           .mockReturnValue([HTTP_STATUS_OK, [], { [X_TOTAL_HEADER]: '0' }]);
 | |
|         tagsApiCallSpy = jest.fn().mockReturnValue([HTTP_STATUS_OK, [], { [X_TOTAL_HEADER]: '0' }]);
 | |
|         commitApiCallSpy = jest.fn().mockReturnValue([HTTP_STATUS_NOT_FOUND]);
 | |
| 
 | |
|         createComponent();
 | |
| 
 | |
|         return waitForRequests();
 | |
|       });
 | |
| 
 | |
|       describe('when the search query is empty', () => {
 | |
|         it('renders a "no results" message', () => {
 | |
|           expect(findNoResults().text()).toBe(DEFAULT_I18N.noResults);
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       describe('when the search query is not empty', () => {
 | |
|         const query = 'hello';
 | |
| 
 | |
|         beforeEach(() => {
 | |
|           updateQuery(query);
 | |
| 
 | |
|           return waitForRequests();
 | |
|         });
 | |
| 
 | |
|         it('renders a "no results" message that includes the search query', () => {
 | |
|           expect(findNoResults().text()).toBe(sprintf(DEFAULT_I18N.noResultsWithQuery, { query }));
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe('branches', () => {
 | |
|       describe('when the branches search returns results', () => {
 | |
|         beforeEach(() => {
 | |
|           createComponent({}, { refType: BRANCH_REF_TYPE, useSymbolicRefNames: true });
 | |
| 
 | |
|           return waitForRequests();
 | |
|         });
 | |
| 
 | |
|         it('renders the branches section in the dropdown', () => {
 | |
|           expect(findBranchesSection().exists()).toBe(true);
 | |
|         });
 | |
| 
 | |
|         it("does not render an error message in the branches section's body", () => {
 | |
|           expect(findErrorListWrapper().exists()).toBe(false);
 | |
|         });
 | |
| 
 | |
|         it('renders the default branch as a selectable item with a "default" badge', () => {
 | |
|           const dropdownItems = findBranchDropdownItems();
 | |
| 
 | |
|           const defaultBranch = fixtures.branches.find((b) => b.default);
 | |
|           const defaultBranchIndex = fixtures.branches.indexOf(defaultBranch);
 | |
| 
 | |
|           expect(trimText(dropdownItems.at(defaultBranchIndex).text())).toBe(
 | |
|             `${defaultBranch.name} default`,
 | |
|           );
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       describe('when the branches search returns no results', () => {
 | |
|         beforeEach(() => {
 | |
|           branchesApiCallSpy = jest
 | |
|             .fn()
 | |
|             .mockReturnValue([HTTP_STATUS_OK, [], { [X_TOTAL_HEADER]: '0' }]);
 | |
| 
 | |
|           createComponent();
 | |
| 
 | |
|           return waitForRequests();
 | |
|         });
 | |
| 
 | |
|         it('does not render the branches section in the dropdown', () => {
 | |
|           expect(findBranchesSection().exists()).toBe(false);
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       describe('when the branches search returns an error', () => {
 | |
|         beforeEach(() => {
 | |
|           branchesApiCallSpy = jest.fn().mockReturnValue([HTTP_STATUS_INTERNAL_SERVER_ERROR]);
 | |
| 
 | |
|           createComponent();
 | |
| 
 | |
|           return waitForRequests();
 | |
|         });
 | |
| 
 | |
|         it('renders the branches section in the dropdown', () => {
 | |
|           expect(findBranchesSection().exists()).toBe(false);
 | |
|         });
 | |
| 
 | |
|         it("renders an error message in the branches section's body", () => {
 | |
|           expect(sectionContainsErrorMessage(DEFAULT_I18N.branchesErrorMessage)).toBe(true);
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe('tags', () => {
 | |
|       describe('when the tags search returns results', () => {
 | |
|         beforeEach(() => {
 | |
|           createComponent({}, { refType: TAG_REF_TYPE, useSymbolicRefNames: true });
 | |
| 
 | |
|           return waitForRequests();
 | |
|         });
 | |
| 
 | |
|         it('renders the tags section in the dropdown', () => {
 | |
|           expect(findTagsSection().exists()).toBe(true);
 | |
|         });
 | |
| 
 | |
|         it('renders the "Tags" heading with a total number indicator', () => {
 | |
|           expect(findTagsSection().find('[role="presentation"]').text()).toMatchInterpolatedText(
 | |
|             `Tags ${fixtures.tags.length}`,
 | |
|           );
 | |
|         });
 | |
| 
 | |
|         it("does not render an error message in the tags section's body", () => {
 | |
|           expect(findErrorListWrapper().exists()).toBe(false);
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       describe('when the tags search returns no results', () => {
 | |
|         beforeEach(() => {
 | |
|           tagsApiCallSpy = jest
 | |
|             .fn()
 | |
|             .mockReturnValue([HTTP_STATUS_OK, [], { [X_TOTAL_HEADER]: '0' }]);
 | |
| 
 | |
|           createComponent();
 | |
| 
 | |
|           return waitForRequests();
 | |
|         });
 | |
| 
 | |
|         it('does not render the tags section in the dropdown', () => {
 | |
|           expect(findTagsSection().exists()).toBe(false);
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       describe('when the tags search returns an error', () => {
 | |
|         beforeEach(() => {
 | |
|           tagsApiCallSpy = jest.fn().mockReturnValue([HTTP_STATUS_INTERNAL_SERVER_ERROR]);
 | |
| 
 | |
|           createComponent();
 | |
| 
 | |
|           return waitForRequests();
 | |
|         });
 | |
| 
 | |
|         it('renders the tags section in the dropdown', () => {
 | |
|           expect(findTagsSection().exists()).toBe(false);
 | |
|         });
 | |
| 
 | |
|         it("renders an error message in the tags section's body", () => {
 | |
|           expect(sectionContainsErrorMessage(DEFAULT_I18N.tagsErrorMessage)).toBe(true);
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe('commits', () => {
 | |
|       describe('when the commit search returns results', () => {
 | |
|         beforeEach(() => {
 | |
|           createComponent();
 | |
| 
 | |
|           updateQuery('abcd1234');
 | |
| 
 | |
|           return waitForRequests();
 | |
|         });
 | |
| 
 | |
|         it('renders the commit section in the dropdown', () => {
 | |
|           expect(findCommitsSection().exists()).toBe(true);
 | |
|         });
 | |
| 
 | |
|         it('renders the "Commits" heading with a total number indicator', () => {
 | |
|           expect(findCommitsSection().find('[role="presentation"]').text()).toMatchInterpolatedText(
 | |
|             `Commits 1`,
 | |
|           );
 | |
|         });
 | |
| 
 | |
|         it("does not render an error message in the commits section's body", () => {
 | |
|           expect(findErrorListWrapper().exists()).toBe(false);
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       describe('when the commit search returns no results (i.e. a 404)', () => {
 | |
|         beforeEach(() => {
 | |
|           commitApiCallSpy = jest.fn().mockReturnValue([HTTP_STATUS_NOT_FOUND]);
 | |
| 
 | |
|           createComponent();
 | |
| 
 | |
|           updateQuery('abcd1234');
 | |
| 
 | |
|           return waitForRequests();
 | |
|         });
 | |
| 
 | |
|         it('does not render the commits section in the dropdown', () => {
 | |
|           expect(findCommitsSection().exists()).toBe(false);
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       describe('when the commit search returns an error (other than a 404)', () => {
 | |
|         beforeEach(() => {
 | |
|           commitApiCallSpy = jest.fn().mockReturnValue([HTTP_STATUS_INTERNAL_SERVER_ERROR]);
 | |
| 
 | |
|           createComponent();
 | |
| 
 | |
|           updateQuery('abcd1234');
 | |
| 
 | |
|           return waitForRequests();
 | |
|         });
 | |
| 
 | |
|         it('renders the commits section in the dropdown', () => {
 | |
|           expect(findCommitsSection().exists()).toBe(false);
 | |
|         });
 | |
| 
 | |
|         it("renders an error message in the commits section's body", () => {
 | |
|           expect(sectionContainsErrorMessage(DEFAULT_I18N.commitsErrorMessage)).toBe(true);
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe('selection', () => {
 | |
|       beforeEach(() => {
 | |
|         createComponent();
 | |
| 
 | |
|         updateQuery(fixtures.commit.short_id);
 | |
| 
 | |
|         return waitForRequests();
 | |
|       });
 | |
| 
 | |
|       describe('when a branch is selected', () => {
 | |
|         it("displays the branch name in the dropdown's button", async () => {
 | |
|           expect(findButtonToggle().text()).toBe(DEFAULT_I18N.noRefSelected);
 | |
| 
 | |
|           await selectFirstBranch();
 | |
| 
 | |
|           expect(findButtonToggle().text()).toBe(fixtures.branches[0].name);
 | |
|         });
 | |
| 
 | |
|         it("updates the v-model binding with the branch's name", async () => {
 | |
|           expect(wrapper.vm.value).toEqual('');
 | |
| 
 | |
|           await selectFirstBranch();
 | |
| 
 | |
|           expect(wrapper.vm.value).toEqual(fixtures.branches[0].name);
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       describe('when a tag is seleceted', () => {
 | |
|         it("displays the tag name in the dropdown's button", async () => {
 | |
|           expect(findButtonToggle().text()).toBe(DEFAULT_I18N.noRefSelected);
 | |
| 
 | |
|           await selectFirstTag();
 | |
| 
 | |
|           expect(findButtonToggle().text()).toBe(fixtures.tags[0].name);
 | |
|         });
 | |
| 
 | |
|         it("updates the v-model binding with the tag's name", async () => {
 | |
|           expect(wrapper.vm.value).toEqual('');
 | |
| 
 | |
|           await selectFirstTag();
 | |
| 
 | |
|           expect(wrapper.vm.value).toEqual(fixtures.tags[0].name);
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       describe('when a commit is selected', () => {
 | |
|         it("displays the full SHA in the dropdown's button", async () => {
 | |
|           expect(findButtonToggle().text()).toBe(DEFAULT_I18N.noRefSelected);
 | |
| 
 | |
|           await selectFirstCommit();
 | |
| 
 | |
|           expect(findButtonToggle().text()).toBe(fixtures.commit.id);
 | |
|         });
 | |
| 
 | |
|         it("updates the v-model binding with the commit's full SHA", async () => {
 | |
|           expect(wrapper.vm.value).toEqual('');
 | |
| 
 | |
|           await selectFirstCommit();
 | |
| 
 | |
|           expect(wrapper.vm.value).toEqual(fixtures.commit.id);
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('with non-default ref types', () => {
 | |
|     it.each`
 | |
|       enabledRefTypes                      | reqsCalled                | reqsNotCalled
 | |
|       ${[REF_TYPE_BRANCHES]}               | ${['branchesApiCallSpy']} | ${['tagsApiCallSpy', 'commitApiCallSpy']}
 | |
|       ${[REF_TYPE_TAGS]}                   | ${['tagsApiCallSpy']}     | ${['branchesApiCallSpy', 'commitApiCallSpy']}
 | |
|       ${[REF_TYPE_COMMITS]}                | ${[]}                     | ${['branchesApiCallSpy', 'tagsApiCallSpy', 'commitApiCallSpy']}
 | |
|       ${[REF_TYPE_TAGS, REF_TYPE_COMMITS]} | ${['tagsApiCallSpy']}     | ${['branchesApiCallSpy', 'commitApiCallSpy']}
 | |
|     `(
 | |
|       'only calls $reqsCalled requests when $enabledRefTypes are enabled',
 | |
|       async ({ enabledRefTypes, reqsCalled, reqsNotCalled }) => {
 | |
|         createComponent({ propsData: { enabledRefTypes } });
 | |
| 
 | |
|         await waitForRequests();
 | |
| 
 | |
|         reqsCalled.forEach((req) => expect(requestSpies[req]).toHaveBeenCalledTimes(1));
 | |
|         reqsNotCalled.forEach((req) => expect(requestSpies[req]).not.toHaveBeenCalled());
 | |
|       },
 | |
|     );
 | |
| 
 | |
|     it('only calls commitApiCallSpy when REF_TYPE_COMMITS is enabled', async () => {
 | |
|       createComponent({ propsData: { enabledRefTypes: [REF_TYPE_COMMITS] } });
 | |
|       updateQuery('abcd1234');
 | |
| 
 | |
|       await waitForRequests();
 | |
| 
 | |
|       expect(commitApiCallSpy).toHaveBeenCalledTimes(1);
 | |
|       expect(branchesApiCallSpy).not.toHaveBeenCalled();
 | |
|       expect(tagsApiCallSpy).not.toHaveBeenCalled();
 | |
|     });
 | |
| 
 | |
|     it('triggers another search if enabled ref types change', async () => {
 | |
|       createComponent({ propsData: { enabledRefTypes: [REF_TYPE_BRANCHES] } });
 | |
|       await waitForRequests();
 | |
| 
 | |
|       expect(branchesApiCallSpy).toHaveBeenCalledTimes(1);
 | |
|       expect(tagsApiCallSpy).not.toHaveBeenCalled();
 | |
| 
 | |
|       wrapper.setProps({
 | |
|         enabledRefTypes: [REF_TYPE_BRANCHES, REF_TYPE_TAGS],
 | |
|       });
 | |
|       await waitForRequests();
 | |
| 
 | |
|       expect(branchesApiCallSpy).toHaveBeenCalledTimes(2);
 | |
|       expect(tagsApiCallSpy).toHaveBeenCalledTimes(1);
 | |
|     });
 | |
| 
 | |
|     it.each`
 | |
|       enabledRefType       | findVisibleSection     | findHiddenSections
 | |
|       ${REF_TYPE_BRANCHES} | ${findBranchesSection} | ${[findTagsSection, findCommitsSection]}
 | |
|       ${REF_TYPE_TAGS}     | ${findTagsSection}     | ${[findBranchesSection, findCommitsSection]}
 | |
|       ${REF_TYPE_COMMITS}  | ${findCommitsSection}  | ${[findBranchesSection, findTagsSection]}
 | |
|     `(
 | |
|       'hides section headers if a single ref type is enabled',
 | |
|       async ({ enabledRefType, findVisibleSection, findHiddenSections }) => {
 | |
|         createComponent({ propsData: { enabledRefTypes: [enabledRefType] } });
 | |
|         updateQuery('abcd1234');
 | |
|         await waitForRequests();
 | |
| 
 | |
|         expect(findVisibleSection().exists()).toBe(true);
 | |
|         expect(findVisibleSection().find('[data-testid="section-header"]').exists()).toBe(false);
 | |
|         findHiddenSections.forEach((findHiddenSection) =>
 | |
|           expect(findHiddenSection().exists()).toBe(false),
 | |
|         );
 | |
|       },
 | |
|     );
 | |
|   });
 | |
| 
 | |
|   describe('validation state', () => {
 | |
|     const invalidClass = 'gl-inset-border-1-red-500!';
 | |
|     const isInvalidClassApplied = () => findListbox().props('toggleClass')[0][invalidClass];
 | |
| 
 | |
|     describe('valid state', () => {
 | |
|       describe('when the state prop is not provided', () => {
 | |
|         it('does not render a red border', () => {
 | |
|           createComponent();
 | |
| 
 | |
|           expect(isInvalidClassApplied()).toBe(false);
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       describe('when the state prop is true', () => {
 | |
|         it('does not render a red border', () => {
 | |
|           createComponent({ propsData: { state: true } });
 | |
| 
 | |
|           expect(isInvalidClassApplied()).toBe(false);
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe('invalid state', () => {
 | |
|       it('renders the dropdown with a red border if the state prop is false', () => {
 | |
|         createComponent({ propsData: { state: false } });
 | |
| 
 | |
|         expect(isInvalidClassApplied()).toBe(true);
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('footer slot', () => {
 | |
|     const footerContent = 'This is the footer content';
 | |
|     const createFooter = jest.fn().mockImplementation(function createMockFooter() {
 | |
|       return this.$createElement('div', { attrs: { 'data-testid': 'footer-content' } }, [
 | |
|         footerContent,
 | |
|       ]);
 | |
|     });
 | |
| 
 | |
|     beforeEach(() => {
 | |
|       createComponent({
 | |
|         scopedSlots: { footer: createFooter },
 | |
|       });
 | |
| 
 | |
|       updateQuery('abcd1234');
 | |
| 
 | |
|       return waitForRequests();
 | |
|     });
 | |
| 
 | |
|     afterEach(() => {
 | |
|       createFooter.mockClear();
 | |
|     });
 | |
| 
 | |
|     it('allows custom content to be shown at the bottom of the dropdown using the footer slot', () => {
 | |
|       expect(wrapper.find(`[data-testid="footer-content"]`).text()).toBe(footerContent);
 | |
|     });
 | |
| 
 | |
|     it('passes the expected slot props', () => {
 | |
|       // The createFooter function gets called every time one of the scoped properties
 | |
|       // is updated. For the sake of this test, we'll just test the last call, which
 | |
|       // represents the final state of the slot props.
 | |
|       const lastCallProps = last(createFooter.mock.calls)[0];
 | |
|       expect(lastCallProps).toMatchSnapshot();
 | |
|     });
 | |
|   });
 | |
| });
 |