diff options
Diffstat (limited to 'spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js')
-rw-r--r-- | spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js | 293 |
1 files changed, 258 insertions, 35 deletions
diff --git a/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js b/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js index fa937100982..d6763a7de41 100644 --- a/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js +++ b/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js @@ -1,11 +1,28 @@ -import { GlDropdown, GlDropdownItem, GlIcon } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { + GlDropdown, + GlDropdownItem, + GlInfiniteScroll, + GlLoadingIcon, + GlSearchBoxByType, +} from '@gitlab/ui'; +import { createLocalVue, mount, shallowMount } from '@vue/test-utils'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import BranchSwitcher from '~/pipeline_editor/components/file_nav/branch_switcher.vue'; import { DEFAULT_FAILURE } from '~/pipeline_editor/constants'; -import { mockDefaultBranch, mockProjectBranches, mockProjectFullPath } from '../../mock_data'; +import getAvailableBranches from '~/pipeline_editor/graphql/queries/available_branches.graphql'; +import { + mockBranchPaginationLimit, + mockDefaultBranch, + mockEmptySearchBranches, + mockProjectBranches, + mockProjectFullPath, + mockSearchBranches, + mockTotalBranches, + mockTotalBranchResults, + mockTotalSearchResults, +} from '../../mock_data'; const localVue = createLocalVue(); localVue.use(VueApollo); @@ -15,30 +32,64 @@ describe('Pipeline editor branch switcher', () => { let mockApollo; let mockAvailableBranchQuery; - const createComponentWithApollo = () => { - const resolvers = { - Query: { - project: mockAvailableBranchQuery, + const createComponent = ( + { isQueryLoading, mountFn, options } = { + isQueryLoading: false, + mountFn: shallowMount, + options: {}, + }, + ) => { + wrapper = mountFn(BranchSwitcher, { + propsData: { + paginationLimit: mockBranchPaginationLimit, }, - }; - - mockApollo = createMockApollo([], resolvers); - wrapper = shallowMount(BranchSwitcher, { - localVue, - apolloProvider: mockApollo, provide: { projectFullPath: mockProjectFullPath, + totalBranches: mockTotalBranches, + }, + mocks: { + $apollo: { + queries: { + availableBranches: { + loading: isQueryLoading, + }, + }, + }, }, data() { return { + branches: ['main'], currentBranch: mockDefaultBranch, }; }, + ...options, + }); + }; + + const createComponentWithApollo = (mountFn = shallowMount) => { + const handlers = [[getAvailableBranches, mockAvailableBranchQuery]]; + mockApollo = createMockApollo(handlers); + + createComponent({ + mountFn, + options: { + localVue, + apolloProvider: mockApollo, + mocks: {}, + data() { + return { + currentBranch: mockDefaultBranch, + }; + }, + }, }); }; const findDropdown = () => wrapper.findComponent(GlDropdown); const findDropdownItems = () => wrapper.findAll(GlDropdownItem); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType); + const findInfiniteScroll = () => wrapper.findComponent(GlInfiniteScroll); beforeEach(() => { mockAvailableBranchQuery = jest.fn(); @@ -48,7 +99,7 @@ describe('Pipeline editor branch switcher', () => { wrapper.destroy(); }); - describe('while querying', () => { + describe('when querying for the first time', () => { beforeEach(() => { createComponentWithApollo(); }); @@ -61,41 +112,31 @@ describe('Pipeline editor branch switcher', () => { describe('after querying', () => { beforeEach(async () => { mockAvailableBranchQuery.mockResolvedValue(mockProjectBranches); - createComponentWithApollo(); + createComponentWithApollo(mount); await waitForPromises(); }); - it('query is called with correct variables', async () => { - expect(mockAvailableBranchQuery).toHaveBeenCalledTimes(1); - expect(mockAvailableBranchQuery).toHaveBeenCalledWith( - expect.anything(), - { - fullPath: mockProjectFullPath, - }, - expect.anything(), - expect.anything(), - ); + it('renders search box', () => { + expect(findSearchBox().exists()).toBe(true); }); it('renders list of branches', () => { expect(findDropdown().exists()).toBe(true); - expect(findDropdownItems()).toHaveLength(mockProjectBranches.repository.branches.length); + expect(findDropdownItems()).toHaveLength(mockTotalBranchResults); }); - it('renders current branch at the top of the list with a check mark', () => { - const firstDropdownItem = findDropdownItems().at(0); - const icon = firstDropdownItem.findComponent(GlIcon); + it('renders current branch with a check mark', () => { + const defaultBranchInDropdown = findDropdownItems().at(0); - expect(firstDropdownItem.text()).toBe(mockDefaultBranch); - expect(icon.exists()).toBe(true); - expect(icon.props('name')).toBe('check'); + expect(defaultBranchInDropdown.text()).toBe(mockDefaultBranch); + expect(defaultBranchInDropdown.props('isChecked')).toBe(true); }); it('does not render check mark for other branches', () => { - const secondDropdownItem = findDropdownItems().at(1); - const icon = secondDropdownItem.findComponent(GlIcon); + const nonDefaultBranch = findDropdownItems().at(1); - expect(icon.classes()).toContain('gl-visibility-hidden'); + expect(nonDefaultBranch.text()).not.toBe(mockDefaultBranch); + expect(nonDefaultBranch.props('isChecked')).toBe(false); }); }); @@ -120,4 +161,186 @@ describe('Pipeline editor branch switcher', () => { ]); }); }); + + describe('when switching branches', () => { + beforeEach(async () => { + jest.spyOn(window.history, 'pushState').mockImplementation(() => {}); + mockAvailableBranchQuery.mockResolvedValue(mockProjectBranches); + createComponentWithApollo(mount); + await waitForPromises(); + }); + + it('updates session history when selecting a different branch', async () => { + const branch = findDropdownItems().at(1); + await branch.vm.$emit('click'); + + expect(window.history.pushState).toHaveBeenCalled(); + expect(window.history.pushState.mock.calls[0][2]).toContain(`?branch_name=${branch.text()}`); + }); + + it('does not update session history when selecting current branch', async () => { + const branch = findDropdownItems().at(0); + await branch.vm.$emit('click'); + + expect(branch.text()).toBe(mockDefaultBranch); + expect(window.history.pushState).not.toHaveBeenCalled(); + }); + + it('emits the refetchContent event when selecting a different branch', async () => { + const branch = findDropdownItems().at(1); + + expect(branch.text()).not.toBe(mockDefaultBranch); + expect(wrapper.emitted('refetchContent')).toBeUndefined(); + + await branch.vm.$emit('click'); + + expect(wrapper.emitted('refetchContent')).toBeDefined(); + expect(wrapper.emitted('refetchContent')).toHaveLength(1); + }); + + it('does not emit the refetchContent event when selecting the current branch', async () => { + const branch = findDropdownItems().at(0); + + expect(branch.text()).toBe(mockDefaultBranch); + expect(wrapper.emitted('refetchContent')).toBeUndefined(); + + await branch.vm.$emit('click'); + + expect(wrapper.emitted('refetchContent')).toBeUndefined(); + }); + }); + + describe('when searching', () => { + beforeEach(async () => { + mockAvailableBranchQuery.mockResolvedValue(mockProjectBranches); + createComponentWithApollo(mount); + await waitForPromises(); + + mockAvailableBranchQuery.mockResolvedValue(mockSearchBranches); + }); + + describe('with a search term', () => { + it('calls query with correct variables', async () => { + findSearchBox().vm.$emit('input', 'te'); + await waitForPromises(); + + expect(mockAvailableBranchQuery).toHaveBeenCalledWith({ + limit: mockTotalBranches, // fetch all branches + offset: 0, + projectFullPath: mockProjectFullPath, + searchPattern: '*te*', + }); + }); + + it('fetches new list of branches', async () => { + expect(findDropdownItems()).toHaveLength(mockTotalBranchResults); + + findSearchBox().vm.$emit('input', 'te'); + await waitForPromises(); + + expect(findDropdownItems()).toHaveLength(mockTotalSearchResults); + }); + + it('does not hide dropdown when search result is empty', async () => { + mockAvailableBranchQuery.mockResolvedValue(mockEmptySearchBranches); + findSearchBox().vm.$emit('input', 'aaaaa'); + await waitForPromises(); + + expect(findDropdown().exists()).toBe(true); + expect(findDropdownItems()).toHaveLength(0); + }); + }); + + describe('without a search term', () => { + beforeEach(async () => { + findSearchBox().vm.$emit('input', 'te'); + await waitForPromises(); + + mockAvailableBranchQuery.mockResolvedValue(mockProjectBranches); + }); + + it('calls query with correct variables', async () => { + findSearchBox().vm.$emit('input', ''); + await waitForPromises(); + + expect(mockAvailableBranchQuery).toHaveBeenCalledWith({ + limit: mockBranchPaginationLimit, // only fetch first n branches first + offset: 0, + projectFullPath: mockProjectFullPath, + searchPattern: '*', + }); + }); + + it('fetches new list of branches', async () => { + expect(findDropdownItems()).toHaveLength(mockTotalSearchResults); + + findSearchBox().vm.$emit('input', ''); + await waitForPromises(); + + expect(findDropdownItems()).toHaveLength(mockTotalBranchResults); + }); + }); + }); + + describe('loading icon', () => { + test.each` + isQueryLoading | isRendered + ${true} | ${true} + ${false} | ${false} + `('checks if query is loading before rendering', ({ isQueryLoading, isRendered }) => { + createComponent({ isQueryLoading, mountFn: mount }); + + expect(findLoadingIcon().exists()).toBe(isRendered); + }); + }); + + describe('when scrolling to the bottom of the list', () => { + beforeEach(async () => { + mockAvailableBranchQuery.mockResolvedValue(mockProjectBranches); + createComponentWithApollo(); + await waitForPromises(); + }); + + afterEach(() => { + mockAvailableBranchQuery.mockClear(); + }); + + describe('when search term is empty', () => { + it('fetches more branches', async () => { + expect(mockAvailableBranchQuery).toHaveBeenCalledTimes(1); + + findInfiniteScroll().vm.$emit('bottomReached'); + await waitForPromises(); + + expect(mockAvailableBranchQuery).toHaveBeenCalledTimes(2); + }); + + it('calls the query with the correct variables', async () => { + findInfiniteScroll().vm.$emit('bottomReached'); + await waitForPromises(); + + expect(mockAvailableBranchQuery).toHaveBeenCalledWith({ + limit: mockBranchPaginationLimit, + offset: mockBranchPaginationLimit, // offset changed + projectFullPath: mockProjectFullPath, + searchPattern: '*', + }); + }); + }); + + describe('when search term exists', () => { + it('does not fetch more branches', async () => { + findSearchBox().vm.$emit('input', 'te'); + await waitForPromises(); + + expect(mockAvailableBranchQuery).toHaveBeenCalledTimes(2); + mockAvailableBranchQuery.mockClear(); + + findInfiniteScroll().vm.$emit('bottomReached'); + await waitForPromises(); + + expect(mockAvailableBranchQuery).not.toHaveBeenCalled(); + }); + }); + }); }); |