diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-23 12:10:13 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-23 12:10:13 +0300 |
commit | 65fdda8d39a9af414dbe5aa3a385b9bcba00960b (patch) | |
tree | 8cdac4fe05966ae74301044522ad5be1e7087ed1 /spec/frontend/boards/project_select_spec.js | |
parent | de64b03b15fb40a3fc2f1897e8e4f6be50fd4403 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/boards/project_select_spec.js')
-rw-r--r-- | spec/frontend/boards/project_select_spec.js | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/spec/frontend/boards/project_select_spec.js b/spec/frontend/boards/project_select_spec.js new file mode 100644 index 00000000000..e187828702e --- /dev/null +++ b/spec/frontend/boards/project_select_spec.js @@ -0,0 +1,267 @@ +import { mount } from '@vue/test-utils'; +import axios from 'axios'; +import AxiosMockAdapter from 'axios-mock-adapter'; +import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlLoadingIcon } from '@gitlab/ui'; +import httpStatus from '~/lib/utils/http_status'; +import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants'; +import { ListType } from '~/boards/constants'; +import eventHub from '~/boards/eventhub'; +import { deprecatedCreateFlash as flash } from '~/flash'; + +import ProjectSelect from '~/boards/components/project_select.vue'; + +import { listObj, mockRawGroupProjects } from './mock_data'; + +jest.mock('~/boards/eventhub'); +jest.mock('~/flash'); + +const dummyGon = { + api_version: 'v4', + relative_url_root: '/gitlab', +}; + +const mockGroupId = 1; +const mockProjectsList1 = mockRawGroupProjects.slice(0, 1); +const mockProjectsList2 = mockRawGroupProjects.slice(1); +const mockDefaultFetchOptions = { + with_issues_enabled: true, + with_shared: false, + include_subgroups: true, + order_by: 'similarity', +}; + +const itemsPerPage = 20; + +describe('ProjectSelect component', () => { + let wrapper; + let axiosMock; + + const findLabel = () => wrapper.find("[data-testid='header-label']"); + const findGlDropdown = () => wrapper.find(GlDropdown); + const findGlDropdownLoadingIcon = () => + findGlDropdown() + .find('button:first-child') + .find(GlLoadingIcon); + const findGlSearchBoxByType = () => wrapper.find(GlSearchBoxByType); + const findGlDropdownItems = () => wrapper.findAll(GlDropdownItem); + const findFirstGlDropdownItem = () => findGlDropdownItems().at(0); + const findInMenuLoadingIcon = () => wrapper.find("[data-testid='dropdown-text-loading-icon']"); + const findEmptySearchMessage = () => wrapper.find("[data-testid='empty-result-message']"); + + const mockGetRequest = (data = [], statusCode = httpStatus.OK) => { + axiosMock + .onGet(`/gitlab/api/v4/groups/${mockGroupId}/projects.json`) + .replyOnce(statusCode, data); + }; + + const searchForProject = async (keyword, waitForAll = true) => { + findGlSearchBoxByType().vm.$emit('input', keyword); + + if (waitForAll) { + await axios.waitForAll(); + } + }; + + const createWrapper = async ({ list = listObj } = {}, waitForAll = true) => { + wrapper = mount(ProjectSelect, { + propsData: { + list, + }, + provide: { + groupId: 1, + }, + }); + + if (waitForAll) { + await axios.waitForAll(); + } + }; + + beforeEach(() => { + axiosMock = new AxiosMockAdapter(axios); + window.gon = dummyGon; + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + axiosMock.restore(); + jest.clearAllMocks(); + }); + + it('displays a header title', async () => { + createWrapper({}); + + expect(findLabel().text()).toBe('Projects'); + }); + + it('renders a default dropdown text', async () => { + createWrapper({}); + + expect(findGlDropdown().exists()).toBe(true); + expect(findGlDropdown().text()).toContain('Select a project'); + }); + + describe('when mounted', () => { + it('displays a loading icon while projects are being fetched', async () => { + mockGetRequest([]); + + createWrapper({}, false); + + expect(findGlDropdownLoadingIcon().exists()).toBe(true); + + await axios.waitForAll(); + + expect(axiosMock.history.get[0].params).toMatchObject({ search: '' }); + expect(axiosMock.history.get[0].url).toBe( + `/gitlab/api/v4/groups/${mockGroupId}/projects.json`, + ); + + expect(findGlDropdownLoadingIcon().exists()).toBe(false); + }); + }); + + describe('when dropdown menu is open', () => { + describe('by default', () => { + beforeEach(async () => { + mockGetRequest(mockProjectsList1); + + await createWrapper(); + }); + + it('shows GlSearchBoxByType with default attributes', () => { + expect(findGlSearchBoxByType().exists()).toBe(true); + expect(findGlSearchBoxByType().vm.$attrs).toMatchObject({ + placeholder: 'Search projects', + debounce: '250', + }); + }); + + it("displays the fetched project's name", () => { + expect(findFirstGlDropdownItem().exists()).toBe(true); + expect(findFirstGlDropdownItem().text()).toContain(mockProjectsList1[0].name); + }); + + it("doesn't render loading icon in the menu", () => { + expect(findInMenuLoadingIcon().isVisible()).toBe(false); + }); + + it('renders empty search result message', async () => { + await createWrapper(); + + expect(findEmptySearchMessage().exists()).toBe(true); + }); + }); + + describe('when a project is selected', () => { + beforeEach(async () => { + mockGetRequest(mockProjectsList1); + + await createWrapper(); + + await findFirstGlDropdownItem() + .find('button') + .trigger('click'); + }); + + it('emits setSelectedProject with correct project metadata', () => { + expect(eventHub.$emit).toHaveBeenCalledWith('setSelectedProject', { + id: mockProjectsList1[0].id, + path: mockProjectsList1[0].path_with_namespace, + name: mockProjectsList1[0].name, + namespacedName: mockProjectsList1[0].name_with_namespace, + }); + }); + + it('renders the name of the selected project', () => { + expect( + findGlDropdown() + .find('.gl-new-dropdown-button-text') + .text(), + ).toBe(mockProjectsList1[0].name); + }); + }); + + describe('when user searches for a project', () => { + beforeEach(async () => { + mockGetRequest(mockProjectsList1); + + await createWrapper(); + }); + + it('calls API with correct parameters with default fetch options', async () => { + await searchForProject('foobar'); + + const expectedApiParams = { + search: 'foobar', + per_page: itemsPerPage, + ...mockDefaultFetchOptions, + }; + + expect(axiosMock.history.get[1].params).toMatchObject(expectedApiParams); + expect(axiosMock.history.get[1].url).toBe( + `/gitlab/api/v4/groups/${mockGroupId}/projects.json`, + ); + }); + + describe("when list type is defined and isn't backlog", () => { + it('calls API with an additional fetch option (min_access_level)', async () => { + axiosMock.reset(); + + await createWrapper({ list: { ...listObj, type: ListType.label } }); + + await searchForProject('foobar'); + + const expectedApiParams = { + search: 'foobar', + per_page: itemsPerPage, + ...mockDefaultFetchOptions, + min_access_level: featureAccessLevel.EVERYONE, + }; + + expect(axiosMock.history.get[1].params).toMatchObject(expectedApiParams); + expect(axiosMock.history.get[1].url).toBe( + `/gitlab/api/v4/groups/${mockGroupId}/projects.json`, + ); + }); + }); + + it('displays and hides gl-loading-icon while and after fetching data', async () => { + await searchForProject('some keyword', false); + + await wrapper.vm.$nextTick(); + + expect(findInMenuLoadingIcon().isVisible()).toBe(true); + + await axios.waitForAll(); + + expect(findInMenuLoadingIcon().isVisible()).toBe(false); + }); + + it('flashes an error message when fetching fails', async () => { + mockGetRequest([], httpStatus.INTERNAL_SERVER_ERROR); + + await searchForProject('foobar'); + + expect(flash).toHaveBeenCalledTimes(1); + expect(flash).toHaveBeenCalledWith('Something went wrong while fetching projects'); + }); + + describe('with non-empty search result', () => { + beforeEach(async () => { + mockGetRequest(mockProjectsList2); + + await searchForProject('foobar'); + }); + + it('displays the retrieved list of projects', async () => { + expect(findFirstGlDropdownItem().text()).toContain(mockProjectsList2[0].name); + }); + + it('does not render empty search result message', async () => { + expect(findEmptySearchMessage().exists()).toBe(false); + }); + }); + }); + }); +}); |