debian-mirror-gitlab/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js
2022-11-25 23:54:43 +05:30

433 lines
13 KiB
JavaScript

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 getAvailableBranchesQuery from '~/pipeline_editor/graphql/queries/available_branches.query.graphql';
import getCurrentBranch from '~/pipeline_editor/graphql/queries/client/current_branch.query.graphql';
import getLastCommitBranch from '~/pipeline_editor/graphql/queries/client/last_commit_branch.query.graphql';
import { resolvers } from '~/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();
});
});
});
});