Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/groups_projects/components/transfer_locations_spec.js')
-rw-r--r--spec/frontend/groups_projects/components/transfer_locations_spec.js377
1 files changed, 377 insertions, 0 deletions
diff --git a/spec/frontend/groups_projects/components/transfer_locations_spec.js b/spec/frontend/groups_projects/components/transfer_locations_spec.js
new file mode 100644
index 00000000000..74424ee3230
--- /dev/null
+++ b/spec/frontend/groups_projects/components/transfer_locations_spec.js
@@ -0,0 +1,377 @@
+import {
+ GlDropdown,
+ GlDropdownItem,
+ GlAlert,
+ GlSearchBoxByType,
+ GlIntersectionObserver,
+ GlLoadingIcon,
+} from '@gitlab/ui';
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import currentUserNamespaceQueryResponse from 'test_fixtures/graphql/projects/settings/current_user_namespace.query.graphql.json';
+import transferLocationsResponsePage1 from 'test_fixtures/api/projects/transfer_locations_page_1.json';
+import transferLocationsResponsePage2 from 'test_fixtures/api/projects/transfer_locations_page_2.json';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { __ } from '~/locale';
+import TransferLocations from '~/groups_projects/components/transfer_locations.vue';
+import { getTransferLocations } from '~/api/projects_api';
+import currentUserNamespaceQuery from '~/projects/settings/graphql/queries/current_user_namespace.query.graphql';
+
+jest.mock('~/api/projects_api', () => ({
+ getTransferLocations: jest.fn(),
+}));
+
+describe('TransferLocations', () => {
+ let wrapper;
+
+ // Default data
+ const resourceId = '1';
+ const defaultPropsData = {
+ groupTransferLocationsApiMethod: getTransferLocations,
+ value: null,
+ };
+ const additionalDropdownItem = {
+ id: -1,
+ humanName: __('No parent group'),
+ };
+
+ // Mock requests
+ const defaultQueryHandler = jest.fn().mockResolvedValue(currentUserNamespaceQueryResponse);
+ const mockResolvedGetTransferLocations = ({
+ data = transferLocationsResponsePage1,
+ page = '1',
+ nextPage = '2',
+ total = '4',
+ totalPages = '2',
+ prevPage = null,
+ } = {}) => {
+ getTransferLocations.mockResolvedValueOnce({
+ data,
+ headers: {
+ 'x-per-page': '2',
+ 'x-page': page,
+ 'x-total': total,
+ 'x-total-pages': totalPages,
+ 'x-next-page': nextPage,
+ 'x-prev-page': prevPage,
+ },
+ });
+ };
+ const mockRejectedGetTransferLocations = () => {
+ const error = new Error();
+
+ getTransferLocations.mockRejectedValueOnce(error);
+ };
+
+ // VTU wrapper helpers
+ Vue.use(VueApollo);
+ const createComponent = ({
+ propsData = {},
+ requestHandlers = [[currentUserNamespaceQuery, defaultQueryHandler]],
+ } = {}) => {
+ wrapper = mountExtended(TransferLocations, {
+ provide: {
+ resourceId,
+ },
+ propsData: {
+ ...defaultPropsData,
+ ...propsData,
+ },
+ apolloProvider: createMockApollo(requestHandlers),
+ });
+ };
+
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const showDropdown = async () => {
+ findDropdown().vm.$emit('show');
+ await waitForPromises();
+ };
+ const findUserTransferLocations = () =>
+ wrapper
+ .findByTestId('user-transfer-locations')
+ .findAllComponents(GlDropdownItem)
+ .wrappers.map((dropdownItem) => dropdownItem.text());
+ const findGroupTransferLocations = () =>
+ wrapper
+ .findByTestId('group-transfer-locations')
+ .findAllComponents(GlDropdownItem)
+ .wrappers.map((dropdownItem) => dropdownItem.text());
+ const findDropdownItemByText = (text) =>
+ wrapper
+ .findAllComponents(GlDropdownItem)
+ .wrappers.find((dropdownItem) => dropdownItem.text() === text);
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findSearch = () => wrapper.findComponent(GlSearchBoxByType);
+ const searchEmitInput = (searchTerm = 'foo') => findSearch().vm.$emit('input', searchTerm);
+ const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver);
+ const intersectionObserverEmitAppear = () => findIntersectionObserver().vm.$emit('appear');
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when `GlDropdown` is opened', () => {
+ it('shows loading icon', async () => {
+ getTransferLocations.mockReturnValueOnce(new Promise(() => {}));
+ createComponent();
+ findDropdown().vm.$emit('show');
+ await nextTick();
+
+ expect(findLoadingIcon().exists()).toBe(true);
+ });
+
+ it('fetches and renders user and group transfer locations', async () => {
+ mockResolvedGetTransferLocations();
+ createComponent();
+ await showDropdown();
+
+ const { namespace } = currentUserNamespaceQueryResponse.data.currentUser;
+
+ expect(findUserTransferLocations()).toEqual([namespace.fullName]);
+ expect(findGroupTransferLocations()).toEqual(
+ transferLocationsResponsePage1.map((transferLocation) => transferLocation.full_name),
+ );
+ });
+
+ describe('when `showUserTransferLocations` prop is `false`', () => {
+ it('does not fetch user transfer locations', async () => {
+ mockResolvedGetTransferLocations();
+ createComponent({
+ propsData: {
+ showUserTransferLocations: false,
+ },
+ });
+ await showDropdown();
+
+ expect(wrapper.findByTestId('user-transfer-locations').exists()).toBe(false);
+ });
+ });
+
+ describe('when `additionalDropdownItems` prop is passed', () => {
+ it('displays additional dropdown items', async () => {
+ mockResolvedGetTransferLocations();
+ createComponent({
+ propsData: {
+ additionalDropdownItems: [additionalDropdownItem],
+ },
+ });
+ await showDropdown();
+
+ expect(findDropdownItemByText(additionalDropdownItem.humanName).exists()).toBe(true);
+ });
+
+ describe('when loading', () => {
+ it('does not display additional dropdown items', async () => {
+ getTransferLocations.mockReturnValueOnce(new Promise(() => {}));
+ createComponent({
+ propsData: {
+ additionalDropdownItems: [additionalDropdownItem],
+ },
+ });
+ findDropdown().vm.$emit('show');
+ await nextTick();
+
+ expect(findDropdownItemByText(additionalDropdownItem.humanName)).toBeUndefined();
+ });
+ });
+ });
+
+ describe('when transfer locations have already been fetched', () => {
+ beforeEach(async () => {
+ mockResolvedGetTransferLocations();
+ createComponent();
+ await showDropdown();
+ });
+
+ it('does not fetch transfer locations', async () => {
+ getTransferLocations.mockClear();
+ defaultQueryHandler.mockClear();
+
+ await showDropdown();
+
+ expect(getTransferLocations).not.toHaveBeenCalled();
+ expect(defaultQueryHandler).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when `getTransferLocations` API call fails', () => {
+ it('displays dismissible error alert', async () => {
+ mockRejectedGetTransferLocations();
+ createComponent();
+ await showDropdown();
+
+ const alert = findAlert();
+
+ expect(alert.exists()).toBe(true);
+
+ alert.vm.$emit('dismiss');
+ await nextTick();
+
+ expect(alert.exists()).toBe(false);
+ });
+ });
+
+ describe('when `currentUser` GraphQL query fails', () => {
+ it('displays error alert', async () => {
+ mockResolvedGetTransferLocations();
+ const error = new Error();
+ createComponent({
+ requestHandlers: [[currentUserNamespaceQuery, jest.fn().mockRejectedValueOnce(error)]],
+ });
+ await showDropdown();
+
+ expect(findAlert().exists()).toBe(true);
+ });
+ });
+ });
+
+ describe('when transfer location is selected', () => {
+ it('displays transfer location as selected', () => {
+ const [{ id, full_name: humanName }] = transferLocationsResponsePage1;
+
+ createComponent({
+ propsData: {
+ value: {
+ id,
+ humanName,
+ },
+ },
+ });
+
+ expect(findDropdown().props('text')).toBe(humanName);
+ });
+ });
+
+ describe('when search is typed in', () => {
+ const transferLocationsResponseSearch = [transferLocationsResponsePage1[0]];
+
+ const arrange = async ({ propsData, searchTerm } = {}) => {
+ mockResolvedGetTransferLocations();
+ createComponent({ propsData });
+ await showDropdown();
+ mockResolvedGetTransferLocations({ data: transferLocationsResponseSearch });
+ searchEmitInput(searchTerm);
+ await nextTick();
+ };
+
+ it('sets `isSearchLoading` prop to `true`', async () => {
+ await arrange();
+
+ expect(findSearch().props('isLoading')).toBe(true);
+ });
+
+ it('passes `search` param to API call and updates group transfer locations', async () => {
+ await arrange();
+
+ await waitForPromises();
+
+ expect(getTransferLocations).toHaveBeenCalledWith(
+ resourceId,
+ expect.objectContaining({ search: 'foo' }),
+ );
+ expect(findGroupTransferLocations()).toEqual(
+ transferLocationsResponseSearch.map((transferLocation) => transferLocation.full_name),
+ );
+ });
+
+ it('does not display additional dropdown items if they do not match the search', async () => {
+ await arrange({
+ propsData: {
+ additionalDropdownItems: [additionalDropdownItem],
+ },
+ });
+ await waitForPromises();
+
+ expect(findDropdownItemByText(additionalDropdownItem.humanName)).toBeUndefined();
+ });
+
+ it('displays additional dropdown items if they match the search', async () => {
+ await arrange({
+ propsData: {
+ additionalDropdownItems: [additionalDropdownItem],
+ },
+ searchTerm: 'No par',
+ });
+ await waitForPromises();
+
+ expect(findDropdownItemByText(additionalDropdownItem.humanName).exists()).toBe(true);
+ });
+ });
+
+ describe('when there are no more pages', () => {
+ it('does not show intersection observer', async () => {
+ mockResolvedGetTransferLocations({
+ data: transferLocationsResponsePage1,
+ nextPage: null,
+ total: '2',
+ totalPages: '1',
+ prevPage: null,
+ });
+ createComponent();
+ await showDropdown();
+
+ expect(findIntersectionObserver().exists()).toBe(false);
+ });
+ });
+
+ describe('when intersection observer appears', () => {
+ const arrange = async () => {
+ mockResolvedGetTransferLocations();
+ createComponent();
+ await showDropdown();
+
+ mockResolvedGetTransferLocations({
+ data: transferLocationsResponsePage2,
+ page: '2',
+ nextPage: null,
+ prevPage: '1',
+ totalPages: '2',
+ });
+
+ intersectionObserverEmitAppear();
+ await nextTick();
+ };
+
+ it('shows loading icon', async () => {
+ await arrange();
+
+ expect(findLoadingIcon().exists()).toBe(true);
+ });
+
+ it('passes `page` param to API call', async () => {
+ await arrange();
+
+ await waitForPromises();
+
+ expect(getTransferLocations).toHaveBeenCalledWith(
+ resourceId,
+ expect.objectContaining({ page: 2 }),
+ );
+ });
+
+ it('updates dropdown with new group transfer locations', async () => {
+ await arrange();
+
+ await waitForPromises();
+
+ expect(findGroupTransferLocations()).toEqual(
+ [...transferLocationsResponsePage1, ...transferLocationsResponsePage2].map(
+ ({ full_name: fullName }) => fullName,
+ ),
+ );
+ });
+ });
+
+ describe('when `label` prop is passed', () => {
+ it('renders label', () => {
+ const label = 'Foo bar';
+
+ createComponent({ propsData: { label } });
+
+ expect(wrapper.findByRole('group', { name: label }).exists()).toBe(true);
+ });
+ });
+});