diff options
Diffstat (limited to 'spec/frontend/import_entities')
-rw-r--r-- | spec/frontend/import_entities/import_groups/components/import_table_spec.js | 120 | ||||
-rw-r--r-- | spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js (renamed from spec/frontend/import_entities/import_groups/components/import_table_row_spec.js) | 155 | ||||
-rw-r--r-- | spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js | 88 |
3 files changed, 157 insertions, 206 deletions
diff --git a/spec/frontend/import_entities/import_groups/components/import_table_spec.js b/spec/frontend/import_entities/import_groups/components/import_table_spec.js index 99ef6d9a7fb..bbd8463e685 100644 --- a/spec/frontend/import_entities/import_groups/components/import_table_spec.js +++ b/spec/frontend/import_entities/import_groups/components/import_table_spec.js @@ -3,21 +3,22 @@ import { GlEmptyState, GlLoadingIcon, GlSearchBoxByClick, - GlSprintf, GlDropdown, GlDropdownItem, + GlTable, } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { mount, createLocalVue } from '@vue/test-utils'; +import { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; +import stubChildren from 'helpers/stub_children'; import { stubComponent } from 'helpers/stub_component'; import waitForPromises from 'helpers/wait_for_promises'; import { STATUSES } from '~/import_entities/constants'; import ImportTable from '~/import_entities/import_groups/components/import_table.vue'; -import ImportTableRow from '~/import_entities/import_groups/components/import_table_row.vue'; +import ImportTargetCell from '~/import_entities/import_groups/components/import_target_cell.vue'; import importGroupsMutation from '~/import_entities/import_groups/graphql/mutations/import_groups.mutation.graphql'; -import setNewNameMutation from '~/import_entities/import_groups/graphql/mutations/set_new_name.mutation.graphql'; -import setTargetNamespaceMutation from '~/import_entities/import_groups/graphql/mutations/set_target_namespace.mutation.graphql'; +import setImportTargetMutation from '~/import_entities/import_groups/graphql/mutations/set_import_target.mutation.graphql'; import PaginationLinks from '~/vue_shared/components/pagination_links.vue'; import { availableNamespacesFixture, generateFakeEntry } from '../graphql/fixtures'; @@ -41,10 +42,15 @@ describe('import table', () => { ]; const FAKE_PAGE_INFO = { page: 1, perPage: 20, total: 40, totalPages: 2 }; - const findImportAllButton = () => wrapper.find('h1').find(GlButton); + const findImportSelectedButton = () => + wrapper.findAllComponents(GlButton).wrappers.find((w) => w.text() === 'Import selected'); const findPaginationDropdown = () => wrapper.findComponent(GlDropdown); const findPaginationDropdownText = () => findPaginationDropdown().find({ ref: 'text' }).text(); + // TODO: remove this ugly approach when + // issue: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1531 + const findTable = () => wrapper.vm.getTableRef(); + const createComponent = ({ bulkImportSourceGroups }) => { apolloProvider = createMockApollo([], { Query: { @@ -58,14 +64,17 @@ describe('import table', () => { }, }); - wrapper = shallowMount(ImportTable, { + wrapper = mount(ImportTable, { propsData: { groupPathRegex: /.*/, sourceUrl: SOURCE_URL, + groupUrlErrorMessage: 'Please choose a group URL with no special characters or spaces.', }, stubs: { - GlSprintf, + ...stubChildren(ImportTable), + GlSprintf: false, GlDropdown: GlDropdownStub, + GlTable: false, }, localVue, apolloProvider, @@ -115,7 +124,7 @@ describe('import table', () => { }); await waitForPromises(); - expect(wrapper.findAll(ImportTableRow)).toHaveLength(FAKE_GROUPS.length); + expect(wrapper.findAll('tbody tr')).toHaveLength(FAKE_GROUPS.length); }); it('does not render status string when result list is empty', async () => { @@ -139,19 +148,32 @@ describe('import table', () => { }); it.each` - event | payload | mutation | variables - ${'update-target-namespace'} | ${'new-namespace'} | ${setTargetNamespaceMutation} | ${{ sourceGroupId: FAKE_GROUP.id, targetNamespace: 'new-namespace' }} - ${'update-new-name'} | ${'new-name'} | ${setNewNameMutation} | ${{ sourceGroupId: FAKE_GROUP.id, newName: 'new-name' }} - ${'import-group'} | ${undefined} | ${importGroupsMutation} | ${{ sourceGroupIds: [FAKE_GROUP.id] }} + event | payload | mutation | variables + ${'update-target-namespace'} | ${'new-namespace'} | ${setImportTargetMutation} | ${{ sourceGroupId: FAKE_GROUP.id, targetNamespace: 'new-namespace', newName: 'group1' }} + ${'update-new-name'} | ${'new-name'} | ${setImportTargetMutation} | ${{ sourceGroupId: FAKE_GROUP.id, targetNamespace: 'root', newName: 'new-name' }} `('correctly maps $event to mutation', async ({ event, payload, mutation, variables }) => { jest.spyOn(apolloProvider.defaultClient, 'mutate'); - wrapper.find(ImportTableRow).vm.$emit(event, payload); + wrapper.find(ImportTargetCell).vm.$emit(event, payload); await waitForPromises(); expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({ mutation, variables, }); }); + + it('invokes importGroups mutation when row button is clicked', async () => { + jest.spyOn(apolloProvider.defaultClient, 'mutate'); + const triggerImportButton = wrapper + .findAllComponents(GlButton) + .wrappers.find((w) => w.text() === 'Import'); + + triggerImportButton.vm.$emit('click'); + await waitForPromises(); + expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({ + mutation: importGroupsMutation, + variables: { sourceGroupIds: [FAKE_GROUP.id] }, + }); + }); }); describe('pagination', () => { @@ -279,16 +301,20 @@ describe('import table', () => { }); }); - describe('import all button', () => { - it('does not exists when no groups available', () => { + describe('bulk operations', () => { + it('import selected button is disabled when no groups selected', async () => { createComponent({ - bulkImportSourceGroups: () => new Promise(() => {}), + bulkImportSourceGroups: () => ({ + nodes: FAKE_GROUPS, + pageInfo: FAKE_PAGE_INFO, + }), }); + await waitForPromises(); - expect(findImportAllButton().exists()).toBe(false); + expect(findImportSelectedButton().props().disabled).toBe(true); }); - it('exists when groups are available for import', async () => { + it('import selected button is enabled when groups were selected for import', async () => { createComponent({ bulkImportSourceGroups: () => ({ nodes: FAKE_GROUPS, @@ -296,16 +322,14 @@ describe('import table', () => { }), }); await waitForPromises(); + wrapper.find(GlTable).vm.$emit('row-selected', [FAKE_GROUPS[0]]); + await nextTick(); - expect(findImportAllButton().exists()).toBe(true); + expect(findImportSelectedButton().props().disabled).toBe(false); }); - it('counts only not-imported groups', async () => { - const NEW_GROUPS = [ - generateFakeEntry({ id: 1, status: STATUSES.NONE }), - generateFakeEntry({ id: 2, status: STATUSES.NONE }), - generateFakeEntry({ id: 3, status: STATUSES.FINISHED }), - ]; + it('does not allow selecting already started groups', async () => { + const NEW_GROUPS = [generateFakeEntry({ id: 1, status: STATUSES.FINISHED })]; createComponent({ bulkImportSourceGroups: () => ({ @@ -315,17 +339,41 @@ describe('import table', () => { }); await waitForPromises(); - expect(findImportAllButton().text()).toMatchInterpolatedText('Import 2 groups'); + findTable().selectRow(0); + await nextTick(); + + expect(findImportSelectedButton().props().disabled).toBe(true); }); - it('disables button when any group has validation errors', async () => { + it('does not allow selecting groups with validation errors', async () => { const NEW_GROUPS = [ - generateFakeEntry({ id: 1, status: STATUSES.NONE }), generateFakeEntry({ id: 2, status: STATUSES.NONE, - validation_errors: [{ field: 'new_name', message: 'test validation error' }], + validation_errors: [{ field: 'new_name', message: 'FAKE_VALIDATION_ERROR' }], }), + ]; + + createComponent({ + bulkImportSourceGroups: () => ({ + nodes: NEW_GROUPS, + pageInfo: FAKE_PAGE_INFO, + }), + }); + await waitForPromises(); + + // TODO: remove this ugly approach when + // issue: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1531 + findTable().selectRow(0); + await nextTick(); + + expect(findImportSelectedButton().props().disabled).toBe(true); + }); + + it('invokes importGroups mutation when import selected button is clicked', async () => { + const NEW_GROUPS = [ + generateFakeEntry({ id: 1, status: STATUSES.NONE }), + generateFakeEntry({ id: 2, status: STATUSES.NONE }), generateFakeEntry({ id: 3, status: STATUSES.FINISHED }), ]; @@ -335,9 +383,19 @@ describe('import table', () => { pageInfo: FAKE_PAGE_INFO, }), }); + jest.spyOn(apolloProvider.defaultClient, 'mutate'); await waitForPromises(); - expect(findImportAllButton().props().disabled).toBe(true); + findTable().selectRow(0); + findTable().selectRow(1); + await nextTick(); + + findImportSelectedButton().vm.$emit('click'); + + expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({ + mutation: importGroupsMutation, + variables: { sourceGroupIds: [NEW_GROUPS[0].id, NEW_GROUPS[1].id] }, + }); }); }); }); diff --git a/spec/frontend/import_entities/import_groups/components/import_table_row_spec.js b/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js index 654a8fd00d3..8231297e594 100644 --- a/spec/frontend/import_entities/import_groups/components/import_table_row_spec.js +++ b/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js @@ -2,19 +2,13 @@ import { GlButton, GlDropdownItem, GlLink, GlFormInput } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; -import createMockApollo from 'helpers/mock_apollo_helper'; import ImportGroupDropdown from '~/import_entities/components/group_dropdown.vue'; import { STATUSES } from '~/import_entities/constants'; -import ImportTableRow from '~/import_entities/import_groups/components/import_table_row.vue'; -import addValidationErrorMutation from '~/import_entities/import_groups/graphql/mutations/add_validation_error.mutation.graphql'; -import removeValidationErrorMutation from '~/import_entities/import_groups/graphql/mutations/remove_validation_error.mutation.graphql'; -import groupAndProjectQuery from '~/import_entities/import_groups/graphql/queries/groupAndProject.query.graphql'; +import ImportTargetCell from '~/import_entities/import_groups/components/import_target_cell.vue'; import { availableNamespacesFixture } from '../graphql/fixtures'; Vue.use(VueApollo); -const { i18n: I18N } = ImportTableRow; - const getFakeGroup = (status) => ({ web_url: 'https://fake.host/', full_path: 'fake_group_1', @@ -28,48 +22,23 @@ const getFakeGroup = (status) => ({ progress: { status }, }); -const EXISTING_GROUP_TARGET_NAMESPACE = 'existing-group'; -const EXISTING_GROUP_PATH = 'existing-path'; -const EXISTING_PROJECT_PATH = 'existing-project-path'; - -describe('import table row', () => { +describe('import target cell', () => { let wrapper; - let apolloProvider; let group; const findByText = (cmp, text) => { return wrapper.findAll(cmp).wrappers.find((node) => node.text().indexOf(text) === 0); }; - const findImportButton = () => findByText(GlButton, 'Import'); const findNameInput = () => wrapper.find(GlFormInput); const findNamespaceDropdown = () => wrapper.find(ImportGroupDropdown); const createComponent = (props) => { - apolloProvider = createMockApollo([ - [ - groupAndProjectQuery, - ({ fullPath }) => { - const existingGroup = - fullPath === `${EXISTING_GROUP_TARGET_NAMESPACE}/${EXISTING_GROUP_PATH}` - ? { id: 1 } - : null; - - const existingProject = - fullPath === `${EXISTING_GROUP_TARGET_NAMESPACE}/${EXISTING_PROJECT_PATH}` - ? { id: 1 } - : null; - - return Promise.resolve({ data: { existingGroup, existingProject } }); - }, - ], - ]); - - wrapper = shallowMount(ImportTableRow, { - apolloProvider, + wrapper = shallowMount(ImportTargetCell, { stubs: { ImportGroupDropdown }, propsData: { availableNamespaces: availableNamespacesFixture, groupPathRegex: /.*/, + groupUrlErrorMessage: 'Please choose a group URL with no special characters or spaces.', ...props, }, }); @@ -86,14 +55,10 @@ describe('import table row', () => { createComponent({ group }); }); - it.each` - selector | sourceEvent | payload | event - ${findNameInput} | ${'input'} | ${'demo'} | ${'update-new-name'} - ${findImportButton} | ${'click'} | ${undefined} | ${'import-group'} - `('invokes $event', ({ selector, sourceEvent, payload, event }) => { - selector().vm.$emit(sourceEvent, payload); - expect(wrapper.emitted(event)).toBeDefined(); - expect(wrapper.emitted(event)[0][0]).toBe(payload); + it('invokes $event', () => { + findNameInput().vm.$emit('input', 'demo'); + expect(wrapper.emitted('update-new-name')).toBeDefined(); + expect(wrapper.emitted('update-new-name')[0][0]).toBe('demo'); }); it('emits update-target-namespace when dropdown option is clicked', () => { @@ -113,10 +78,6 @@ describe('import table row', () => { createComponent({ group }); }); - it('renders Import button', () => { - expect(findByText(GlButton, 'Import').exists()).toBe(true); - }); - it('renders namespace dropdown as not disabled', () => { expect(findNamespaceDropdown().attributes('disabled')).toBe(undefined); }); @@ -198,7 +159,9 @@ describe('import table row', () => { groupPathRegex: /^[a-zA-Z]+$/, }); - expect(wrapper.text()).toContain('Please choose a group URL with no special characters.'); + expect(wrapper.text()).toContain( + 'Please choose a group URL with no special characters or spaces.', + ); }); it('reports invalid group name if relevant validation error exists', async () => { @@ -221,101 +184,5 @@ describe('import table row', () => { expect(wrapper.text()).toContain(FAKE_ERROR_MESSAGE); }); - - it('sets validation error when targetting existing group', async () => { - const testGroup = getFakeGroup(STATUSES.NONE); - - createComponent({ - group: { - ...testGroup, - import_target: { - target_namespace: EXISTING_GROUP_TARGET_NAMESPACE, - new_name: EXISTING_GROUP_PATH, - }, - }, - }); - - jest.spyOn(wrapper.vm.$apollo, 'mutate'); - - jest.runOnlyPendingTimers(); - await nextTick(); - - expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ - mutation: addValidationErrorMutation, - variables: { - field: 'new_name', - message: I18N.NAME_ALREADY_EXISTS, - sourceGroupId: testGroup.id, - }, - }); - }); - - it('sets validation error when targetting existing project', async () => { - const testGroup = getFakeGroup(STATUSES.NONE); - - createComponent({ - group: { - ...testGroup, - import_target: { - target_namespace: EXISTING_GROUP_TARGET_NAMESPACE, - new_name: EXISTING_PROJECT_PATH, - }, - }, - }); - - jest.spyOn(wrapper.vm.$apollo, 'mutate'); - - jest.runOnlyPendingTimers(); - await nextTick(); - - expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ - mutation: addValidationErrorMutation, - variables: { - field: 'new_name', - message: I18N.NAME_ALREADY_EXISTS, - sourceGroupId: testGroup.id, - }, - }); - }); - - it('clears validation error when target is updated', async () => { - const testGroup = getFakeGroup(STATUSES.NONE); - - createComponent({ - group: { - ...testGroup, - import_target: { - target_namespace: EXISTING_GROUP_TARGET_NAMESPACE, - new_name: EXISTING_PROJECT_PATH, - }, - }, - }); - - jest.runOnlyPendingTimers(); - await nextTick(); - - jest.spyOn(wrapper.vm.$apollo, 'mutate'); - - await wrapper.setProps({ - group: { - ...testGroup, - import_target: { - target_namespace: 'valid_namespace', - new_name: 'valid_path', - }, - }, - }); - - jest.runOnlyPendingTimers(); - await nextTick(); - - expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ - mutation: removeValidationErrorMutation, - variables: { - field: 'new_name', - sourceGroupId: testGroup.id, - }, - }); - }); }); }); diff --git a/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js b/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js index ef83c9ebbc4..ec50dfd037f 100644 --- a/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js +++ b/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js @@ -12,12 +12,12 @@ import addValidationErrorMutation from '~/import_entities/import_groups/graphql/ import importGroupsMutation from '~/import_entities/import_groups/graphql/mutations/import_groups.mutation.graphql'; import removeValidationErrorMutation from '~/import_entities/import_groups/graphql/mutations/remove_validation_error.mutation.graphql'; import setImportProgressMutation from '~/import_entities/import_groups/graphql/mutations/set_import_progress.mutation.graphql'; -import setNewNameMutation from '~/import_entities/import_groups/graphql/mutations/set_new_name.mutation.graphql'; -import setTargetNamespaceMutation from '~/import_entities/import_groups/graphql/mutations/set_target_namespace.mutation.graphql'; +import setImportTargetMutation from '~/import_entities/import_groups/graphql/mutations/set_import_target.mutation.graphql'; import updateImportStatusMutation from '~/import_entities/import_groups/graphql/mutations/update_import_status.mutation.graphql'; import availableNamespacesQuery from '~/import_entities/import_groups/graphql/queries/available_namespaces.query.graphql'; import bulkImportSourceGroupQuery from '~/import_entities/import_groups/graphql/queries/bulk_import_source_group.query.graphql'; import bulkImportSourceGroupsQuery from '~/import_entities/import_groups/graphql/queries/bulk_import_source_groups.query.graphql'; +import groupAndProjectQuery from '~/import_entities/import_groups/graphql/queries/group_and_project.query.graphql'; import { StatusPoller } from '~/import_entities/import_groups/graphql/services/status_poller'; import axios from '~/lib/utils/axios_utils'; @@ -38,18 +38,29 @@ const FAKE_ENDPOINTS = { jobs: '/fake_jobs', }; +const FAKE_GROUP_AND_PROJECTS_QUERY_HANDLER = jest.fn().mockResolvedValue({ + data: { + existingGroup: null, + existingProject: null, + }, +}); + describe('Bulk import resolvers', () => { let axiosMockAdapter; let client; const createClient = (extraResolverArgs) => { - return createMockClient({ + const mockedClient = createMockClient({ cache: new InMemoryCache({ fragmentMatcher: { match: () => true }, addTypename: false, }), resolvers: createResolvers({ endpoints: FAKE_ENDPOINTS, ...extraResolverArgs }), }); + + mockedClient.setRequestHandler(groupAndProjectQuery, FAKE_GROUP_AND_PROJECTS_QUERY_HANDLER); + + return mockedClient; }; beforeEach(() => { @@ -196,6 +207,12 @@ describe('Bulk import resolvers', () => { const [statusPoller] = StatusPoller.mock.instances; expect(statusPoller.startPolling).toHaveBeenCalled(); }); + + it('requests validation status when request completes', async () => { + expect(FAKE_GROUP_AND_PROJECTS_QUERY_HANDLER).not.toHaveBeenCalled(); + jest.runOnlyPendingTimers(); + expect(FAKE_GROUP_AND_PROJECTS_QUERY_HANDLER).toHaveBeenCalled(); + }); }); it.each` @@ -256,40 +273,49 @@ describe('Bulk import resolvers', () => { }); }); - it('setTargetNamespaces updates group target namespace', async () => { - const NEW_TARGET_NAMESPACE = 'target'; - const { - data: { - setTargetNamespace: { - id: idInResponse, - import_target: { target_namespace: namespaceInResponse }, + describe('setImportTarget', () => { + it('updates group target namespace and name', async () => { + const NEW_TARGET_NAMESPACE = 'target'; + const NEW_NAME = 'new'; + + const { + data: { + setImportTarget: { + id: idInResponse, + import_target: { target_namespace: namespaceInResponse, new_name: newNameInResponse }, + }, }, - }, - } = await client.mutate({ - mutation: setTargetNamespaceMutation, - variables: { sourceGroupId: GROUP_ID, targetNamespace: NEW_TARGET_NAMESPACE }, + } = await client.mutate({ + mutation: setImportTargetMutation, + variables: { + sourceGroupId: GROUP_ID, + targetNamespace: NEW_TARGET_NAMESPACE, + newName: NEW_NAME, + }, + }); + + expect(idInResponse).toBe(GROUP_ID); + expect(namespaceInResponse).toBe(NEW_TARGET_NAMESPACE); + expect(newNameInResponse).toBe(NEW_NAME); }); - expect(idInResponse).toBe(GROUP_ID); - expect(namespaceInResponse).toBe(NEW_TARGET_NAMESPACE); - }); + it('invokes validation', async () => { + const NEW_TARGET_NAMESPACE = 'target'; + const NEW_NAME = 'new'; - it('setNewName updates group target name', async () => { - const NEW_NAME = 'new'; - const { - data: { - setNewName: { - id: idInResponse, - import_target: { new_name: nameInResponse }, + await client.mutate({ + mutation: setImportTargetMutation, + variables: { + sourceGroupId: GROUP_ID, + targetNamespace: NEW_TARGET_NAMESPACE, + newName: NEW_NAME, }, - }, - } = await client.mutate({ - mutation: setNewNameMutation, - variables: { sourceGroupId: GROUP_ID, newName: NEW_NAME }, - }); + }); - expect(idInResponse).toBe(GROUP_ID); - expect(nameInResponse).toBe(NEW_NAME); + expect(FAKE_GROUP_AND_PROJECTS_QUERY_HANDLER).toHaveBeenCalledWith({ + fullPath: `${NEW_TARGET_NAMESPACE}/${NEW_NAME}`, + }); + }); }); describe('importGroup', () => { |