summaryrefslogtreecommitdiff
path: root/spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js')
-rw-r--r--spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js432
1 files changed, 432 insertions, 0 deletions
diff --git a/spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js b/spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js
new file mode 100644
index 00000000000..a26232df58f
--- /dev/null
+++ b/spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js
@@ -0,0 +1,432 @@
+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 '~/ci/pipeline_editor/components/file_nav/branch_switcher.vue';
+import { DEFAULT_FAILURE } from '~/ci/pipeline_editor/constants';
+import getAvailableBranchesQuery from '~/ci/pipeline_editor/graphql/queries/available_branches.query.graphql';
+import getCurrentBranch from '~/ci/pipeline_editor/graphql/queries/client/current_branch.query.graphql';
+import getLastCommitBranch from '~/ci/pipeline_editor/graphql/queries/client/last_commit_branch.query.graphql';
+import { resolvers } from '~/ci/pipeline_editor/graphql/resolvers';
+
+import {
+ mockBranchPaginationLimit,
+ mockDefaultBranch,
+ mockEmptySearchBranches,
+ mockProjectBranches,
+ mockProjectFullPath,
+ mockSearchBranches,
+ mockTotalBranches,
+ mockTotalBranchResults,
+ mockTotalSearchResults,
+} from '../../mock_data';
+
+const localVue = createLocalVue();
+localVue.use(VueApollo);
+
+describe('Pipeline editor branch switcher', () => {
+ let wrapper;
+ let mockApollo;
+ let mockAvailableBranchQuery;
+
+ const createComponent = ({
+ currentBranch = mockDefaultBranch,
+ availableBranches = ['main'],
+ isQueryLoading = false,
+ mountFn = shallowMount,
+ options = {},
+ props = {},
+ } = {}) => {
+ wrapper = mountFn(BranchSwitcher, {
+ propsData: {
+ ...props,
+ paginationLimit: mockBranchPaginationLimit,
+ },
+ provide: {
+ projectFullPath: mockProjectFullPath,
+ totalBranches: mockTotalBranches,
+ },
+ mocks: {
+ $apollo: {
+ queries: {
+ availableBranches: {
+ loading: isQueryLoading,
+ },
+ },
+ },
+ },
+ data() {
+ return {
+ availableBranches,
+ currentBranch,
+ };
+ },
+ ...options,
+ });
+ };
+
+ const createComponentWithApollo = ({
+ mountFn = shallowMount,
+ props = {},
+ availableBranches = ['main'],
+ } = {}) => {
+ const handlers = [[getAvailableBranchesQuery, mockAvailableBranchQuery]];
+ mockApollo = createMockApollo(handlers, resolvers);
+
+ mockApollo.clients.defaultClient.cache.writeQuery({
+ query: getCurrentBranch,
+ data: {
+ workBranches: {
+ __typename: 'BranchList',
+ current: {
+ __typename: 'WorkBranch',
+ name: mockDefaultBranch,
+ },
+ },
+ },
+ });
+
+ mockApollo.clients.defaultClient.cache.writeQuery({
+ query: getLastCommitBranch,
+ data: {
+ workBranches: {
+ __typename: 'BranchList',
+ lastCommit: {
+ __typename: 'WorkBranch',
+ name: '',
+ },
+ },
+ },
+ });
+
+ createComponent({
+ mountFn,
+ props,
+ availableBranches,
+ options: {
+ localVue,
+ apolloProvider: mockApollo,
+ mocks: {},
+ },
+ });
+ };
+
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+ const findInfiniteScroll = () => wrapper.findComponent(GlInfiniteScroll);
+ const defaultBranchInDropdown = () => findDropdownItems().at(0);
+
+ const setAvailableBranchesMock = (availableBranches) => {
+ mockAvailableBranchQuery.mockResolvedValue(availableBranches);
+ };
+
+ beforeEach(() => {
+ mockAvailableBranchQuery = jest.fn();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const testErrorHandling = () => {
+ expect(wrapper.emitted('showError')).toBeDefined();
+ expect(wrapper.emitted('showError')[0]).toEqual([
+ {
+ reasons: [wrapper.vm.$options.i18n.fetchError],
+ type: DEFAULT_FAILURE,
+ },
+ ]);
+ };
+
+ describe('when querying for the first time', () => {
+ beforeEach(() => {
+ createComponentWithApollo({ availableBranches: [] });
+ });
+
+ it('disables the dropdown', () => {
+ expect(findDropdown().props('disabled')).toBe(true);
+ });
+ });
+
+ describe('after querying', () => {
+ beforeEach(async () => {
+ setAvailableBranchesMock(mockProjectBranches);
+ createComponentWithApollo({ mountFn: mount });
+ await waitForPromises();
+ });
+
+ it('renders search box', () => {
+ expect(findSearchBox().exists()).toBe(true);
+ });
+
+ it('renders list of branches', () => {
+ expect(findDropdown().exists()).toBe(true);
+ expect(findDropdownItems()).toHaveLength(mockTotalBranchResults);
+ });
+
+ it('renders current branch with a check mark', () => {
+ expect(defaultBranchInDropdown().text()).toBe(mockDefaultBranch);
+ expect(defaultBranchInDropdown().props('isChecked')).toBe(true);
+ });
+
+ it('does not render check mark for other branches', () => {
+ const nonDefaultBranch = findDropdownItems().at(1);
+
+ expect(nonDefaultBranch.text()).not.toBe(mockDefaultBranch);
+ expect(nonDefaultBranch.props('isChecked')).toBe(false);
+ });
+ });
+
+ describe('on fetch error', () => {
+ beforeEach(async () => {
+ setAvailableBranchesMock(new Error());
+ createComponentWithApollo({ availableBranches: [] });
+ await waitForPromises();
+ });
+
+ it('does not render dropdown', () => {
+ expect(findDropdown().props('disabled')).toBe(true);
+ });
+
+ it('shows an error message', () => {
+ testErrorHandling();
+ });
+ });
+
+ describe('when switching branches', () => {
+ beforeEach(async () => {
+ jest.spyOn(window.history, 'pushState').mockImplementation(() => {});
+ setAvailableBranchesMock(mockProjectBranches);
+ createComponentWithApollo({ mountFn: mount });
+ await waitForPromises();
+ });
+
+ it('updates session history when selecting a different branch', async () => {
+ const branch = findDropdownItems().at(1);
+ branch.vm.$emit('click');
+ await waitForPromises();
+
+ 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);
+ branch.vm.$emit('click');
+ await waitForPromises();
+
+ 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();
+
+ branch.vm.$emit('click');
+ await waitForPromises();
+
+ 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();
+
+ branch.vm.$emit('click');
+ await waitForPromises();
+
+ expect(wrapper.emitted('refetchContent')).toBeUndefined();
+ });
+
+ describe('with unsaved changes', () => {
+ beforeEach(async () => {
+ createComponentWithApollo({ mountFn: mount, props: { hasUnsavedChanges: true } });
+ await waitForPromises();
+ });
+
+ it('emits `select-branch` event and does not switch branch', async () => {
+ expect(wrapper.emitted('select-branch')).toBeUndefined();
+
+ const branch = findDropdownItems().at(1);
+ await branch.vm.$emit('click');
+
+ expect(wrapper.emitted('select-branch')).toEqual([[branch.text()]]);
+ expect(wrapper.emitted('refetchContent')).toBeUndefined();
+ });
+ });
+ });
+
+ describe('when searching', () => {
+ beforeEach(async () => {
+ setAvailableBranchesMock(mockProjectBranches);
+ createComponentWithApollo({ mountFn: mount });
+ await waitForPromises();
+ });
+
+ afterEach(() => {
+ mockAvailableBranchQuery.mockClear();
+ });
+
+ it('shows error message on fetch error', async () => {
+ mockAvailableBranchQuery.mockResolvedValue(new Error());
+
+ findSearchBox().vm.$emit('input', 'te');
+ await waitForPromises();
+
+ testErrorHandling();
+ });
+
+ describe('with a search term', () => {
+ beforeEach(async () => {
+ mockAvailableBranchQuery.mockResolvedValue(mockSearchBranches);
+ });
+
+ 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 () => {
+ mockAvailableBranchQuery.mockResolvedValue(mockSearchBranches);
+ 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', () => {
+ it.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 () => {
+ setAvailableBranchesMock(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: '*',
+ });
+ });
+
+ it('shows error message on fetch error', async () => {
+ mockAvailableBranchQuery.mockResolvedValue(new Error());
+
+ findInfiniteScroll().vm.$emit('bottomReached');
+ await waitForPromises();
+
+ testErrorHandling();
+ });
+ });
+
+ 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();
+ });
+ });
+ });
+});