diff options
Diffstat (limited to 'spec/frontend/import_projects')
9 files changed, 378 insertions, 338 deletions
diff --git a/spec/frontend/import_projects/components/bitbucket_status_table_spec.js b/spec/frontend/import_projects/components/bitbucket_status_table_spec.js index 132ccd0e324..b65b388fd5f 100644 --- a/spec/frontend/import_projects/components/bitbucket_status_table_spec.js +++ b/spec/frontend/import_projects/components/bitbucket_status_table_spec.js @@ -33,7 +33,7 @@ describe('BitbucketStatusTable', () => { it('renders import table component', () => { createComponent({ providerTitle: 'Test' }); - expect(wrapper.contains(ImportProjectsTable)).toBe(true); + expect(wrapper.find(ImportProjectsTable).exists()).toBe(true); }); it('passes alert in incompatible-repos-warning slot', () => { diff --git a/spec/frontend/import_projects/components/import_projects_table_spec.js b/spec/frontend/import_projects/components/import_projects_table_spec.js index b217242968a..1dbad588ec4 100644 --- a/spec/frontend/import_projects/components/import_projects_table_spec.js +++ b/spec/frontend/import_projects/components/import_projects_table_spec.js @@ -1,15 +1,12 @@ import { nextTick } from 'vue'; import Vuex from 'vuex'; import { createLocalVue, shallowMount } from '@vue/test-utils'; -import { GlLoadingIcon, GlButton } from '@gitlab/ui'; +import { GlLoadingIcon, GlButton, GlIntersectionObserver } from '@gitlab/ui'; import state from '~/import_projects/store/state'; import * as getters from '~/import_projects/store/getters'; import { STATUSES } from '~/import_projects/constants'; import ImportProjectsTable from '~/import_projects/components/import_projects_table.vue'; -import ImportedProjectTableRow from '~/import_projects/components/imported_project_table_row.vue'; import ProviderRepoTableRow from '~/import_projects/components/provider_repo_table_row.vue'; -import IncompatibleRepoTableRow from '~/import_projects/components/incompatible_repo_table_row.vue'; -import PageQueryParamSync from '~/import_projects/components/page_query_param_sync.vue'; describe('ImportProjectsTable', () => { let wrapper; @@ -18,16 +15,26 @@ describe('ImportProjectsTable', () => { wrapper.find('input[data-qa-selector="githubish_import_filter_field"]'); const providerTitle = 'THE PROVIDER'; - const providerRepo = { id: 10, sanitizedName: 'sanitizedName', fullName: 'fullName' }; + const providerRepo = { + importSource: { + id: 10, + sanitizedName: 'sanitizedName', + fullName: 'fullName', + }, + importedProject: null, + }; const findImportAllButton = () => wrapper .findAll(GlButton) .filter(w => w.props().variant === 'success') .at(0); + const findImportAllModal = () => wrapper.find({ ref: 'importAllModal' }); const importAllFn = jest.fn(); + const importAllModalShowFn = jest.fn(); const setPageFn = jest.fn(); + const fetchReposFn = jest.fn(); function createComponent({ state: initialState, @@ -46,7 +53,7 @@ describe('ImportProjectsTable', () => { ...customGetters, }, actions: { - fetchRepos: jest.fn(), + fetchRepos: fetchReposFn, fetchJobs: jest.fn(), fetchNamespaces: jest.fn(), importAll: importAllFn, @@ -66,6 +73,9 @@ describe('ImportProjectsTable', () => { paginatable, }, slots, + stubs: { + GlModal: { template: '<div>Modal!</div>', methods: { show: importAllModalShowFn } }, + }, }); } @@ -79,58 +89,54 @@ describe('ImportProjectsTable', () => { it('renders a loading icon while repos are loading', () => { createComponent({ state: { isLoadingRepos: true } }); - expect(wrapper.contains(GlLoadingIcon)).toBe(true); + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); }); it('renders a loading icon while namespaces are loading', () => { createComponent({ state: { isLoadingNamespaces: true } }); - expect(wrapper.contains(GlLoadingIcon)).toBe(true); + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); }); - it('renders a table with imported projects and provider repos', () => { + it('renders a table with provider repos', () => { + const repositories = [ + { importSource: { id: 1 }, importedProject: null }, + { importSource: { id: 2 }, importedProject: { importStatus: STATUSES.FINISHED } }, + { importSource: { id: 3, incompatible: true }, importedProject: {} }, + ]; + createComponent({ - state: { - namespaces: [{ fullPath: 'path' }], - repositories: [ - { importSource: { id: 1 }, importedProject: null, importStatus: STATUSES.NONE }, - { importSource: { id: 2 }, importedProject: {}, importStatus: STATUSES.FINISHED }, - { - importSource: { id: 3, incompatible: true }, - importedProject: {}, - importStatus: STATUSES.NONE, - }, - ], - }, + state: { namespaces: [{ fullPath: 'path' }], repositories }, }); - expect(wrapper.contains(GlLoadingIcon)).toBe(false); - expect(wrapper.contains('table')).toBe(true); + expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); + expect(wrapper.find('table').exists()).toBe(true); expect( wrapper .findAll('th') .filter(w => w.text() === `From ${providerTitle}`) - .isEmpty(), - ).toBe(false); + .exists(), + ).toBe(true); - expect(wrapper.contains(ProviderRepoTableRow)).toBe(true); - expect(wrapper.contains(ImportedProjectTableRow)).toBe(true); - expect(wrapper.contains(IncompatibleRepoTableRow)).toBe(true); + expect(wrapper.findAll(ProviderRepoTableRow)).toHaveLength(repositories.length); }); it.each` - hasIncompatibleRepos | buttonText - ${false} | ${'Import all repositories'} - ${true} | ${'Import all compatible repositories'} + hasIncompatibleRepos | count | buttonText + ${false} | ${1} | ${'Import 1 repository'} + ${true} | ${1} | ${'Import 1 compatible repository'} + ${false} | ${5} | ${'Import 5 repositories'} + ${true} | ${5} | ${'Import 5 compatible repositories'} `( - 'import all button has "$buttonText" text when hasIncompatibleRepos is $hasIncompatibleRepos', - ({ hasIncompatibleRepos, buttonText }) => { + 'import all button has "$buttonText" text when hasIncompatibleRepos is $hasIncompatibleRepos and repos count is $count', + ({ hasIncompatibleRepos, buttonText, count }) => { createComponent({ state: { providerRepos: [providerRepo], }, getters: { hasIncompatibleRepos: () => hasIncompatibleRepos, + importAllCount: () => count, }, }); @@ -138,20 +144,28 @@ describe('ImportProjectsTable', () => { }, ); - it('renders an empty state if there are no projects available', () => { + it('renders an empty state if there are no repositories available', () => { createComponent({ state: { repositories: [] } }); - expect(wrapper.contains(ProviderRepoTableRow)).toBe(false); - expect(wrapper.contains(ImportedProjectTableRow)).toBe(false); + expect(wrapper.find(ProviderRepoTableRow).exists()).toBe(false); expect(wrapper.text()).toContain(`No ${providerTitle} repositories found`); }); - it('sends importAll event when import button is clicked', async () => { - createComponent({ state: { providerRepos: [providerRepo] } }); + it('opens confirmation modal when import all button is clicked', async () => { + createComponent({ state: { repositories: [providerRepo] } }); findImportAllButton().vm.$emit('click'); await nextTick(); + expect(importAllModalShowFn).toHaveBeenCalled(); + }); + + it('triggers importAll action when modal is confirmed', async () => { + createComponent({ state: { providerRepos: [providerRepo] } }); + + findImportAllModal().vm.$emit('ok'); + await nextTick(); + expect(importAllFn).toHaveBeenCalled(); }); @@ -189,21 +203,29 @@ describe('ImportProjectsTable', () => { }); }); - it('passes current page to page-query-param-sync component', () => { - expect(wrapper.find(PageQueryParamSync).props().page).toBe(pageInfo.page); + it('does not call fetchRepos on mount', () => { + expect(fetchReposFn).not.toHaveBeenCalled(); + }); + + it('renders intersection observer component', () => { + expect(wrapper.find(GlIntersectionObserver).exists()).toBe(true); }); - it('dispatches setPage when page-query-param-sync emits popstate', () => { - const NEW_PAGE = 2; - wrapper.find(PageQueryParamSync).vm.$emit('popstate', NEW_PAGE); + it('calls fetchRepos when intersection observer appears', async () => { + wrapper.find(GlIntersectionObserver).vm.$emit('appear'); - const { calls } = setPageFn.mock; + await nextTick(); - expect(calls).toHaveLength(1); - expect(calls[0][1]).toBe(NEW_PAGE); + expect(fetchReposFn).toHaveBeenCalled(); }); }); + it('calls fetchRepos on mount', () => { + createComponent(); + + expect(fetchReposFn).toHaveBeenCalled(); + }); + it.each` hasIncompatibleRepos | shouldRenderSlot | action ${false} | ${false} | ${'does not render'} diff --git a/spec/frontend/import_projects/components/imported_project_table_row_spec.js b/spec/frontend/import_projects/components/imported_project_table_row_spec.js deleted file mode 100644 index 8890c352826..00000000000 --- a/spec/frontend/import_projects/components/imported_project_table_row_spec.js +++ /dev/null @@ -1,44 +0,0 @@ -import { mount } from '@vue/test-utils'; -import ImportedProjectTableRow from '~/import_projects/components/imported_project_table_row.vue'; -import ImportStatus from '~/import_projects/components/import_status.vue'; -import { STATUSES } from '~/import_projects/constants'; - -describe('ImportedProjectTableRow', () => { - let wrapper; - const project = { - importSource: { - fullName: 'fullName', - providerLink: 'providerLink', - }, - importedProject: { - id: 1, - fullPath: 'fullPath', - importSource: 'importSource', - }, - importStatus: STATUSES.FINISHED, - }; - - function mountComponent() { - wrapper = mount(ImportedProjectTableRow, { propsData: { project } }); - } - - beforeEach(() => { - mountComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders an imported project table row', () => { - const providerLink = wrapper.find('[data-testid=providerLink]'); - - expect(providerLink.attributes().href).toMatch(project.importSource.providerLink); - expect(providerLink.text()).toMatch(project.importSource.fullName); - expect(wrapper.find('[data-testid=fullPath]').text()).toMatch(project.importedProject.fullPath); - expect(wrapper.find(ImportStatus).props().status).toBe(project.importStatus); - expect(wrapper.find('[data-testid=goToProject').attributes().href).toMatch( - project.importedProject.fullPath, - ); - }); -}); diff --git a/spec/frontend/import_projects/components/page_query_param_sync_spec.js b/spec/frontend/import_projects/components/page_query_param_sync_spec.js deleted file mode 100644 index be19ecca1ba..00000000000 --- a/spec/frontend/import_projects/components/page_query_param_sync_spec.js +++ /dev/null @@ -1,87 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import { TEST_HOST } from 'helpers/test_constants'; - -import PageQueryParamSync from '~/import_projects/components/page_query_param_sync.vue'; - -describe('PageQueryParamSync', () => { - let originalPushState; - let originalAddEventListener; - let originalRemoveEventListener; - - const pushStateMock = jest.fn(); - const addEventListenerMock = jest.fn(); - const removeEventListenerMock = jest.fn(); - - beforeAll(() => { - window.location.search = ''; - originalPushState = window.pushState; - - window.history.pushState = pushStateMock; - - originalAddEventListener = window.addEventListener; - window.addEventListener = addEventListenerMock; - - originalRemoveEventListener = window.removeEventListener; - window.removeEventListener = removeEventListenerMock; - }); - - afterAll(() => { - window.history.pushState = originalPushState; - window.addEventListener = originalAddEventListener; - window.removeEventListener = originalRemoveEventListener; - }); - - let wrapper; - beforeEach(() => { - wrapper = shallowMount(PageQueryParamSync, { - propsData: { page: 3 }, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('calls push state with page number when page is updated and differs from 1', async () => { - wrapper.setProps({ page: 2 }); - - await nextTick(); - - const { calls } = pushStateMock.mock; - expect(calls).toHaveLength(1); - expect(calls[0][2]).toBe(`${TEST_HOST}/?page=2`); - }); - - it('calls push state without page number when page is updated and is 1', async () => { - wrapper.setProps({ page: 1 }); - - await nextTick(); - - const { calls } = pushStateMock.mock; - expect(calls).toHaveLength(1); - expect(calls[0][2]).toBe(`${TEST_HOST}/`); - }); - - it('subscribes to popstate event on create', () => { - expect(addEventListenerMock).toHaveBeenCalledWith('popstate', expect.any(Function)); - }); - - it('unsubscribes from popstate event when destroyed', () => { - const [, fn] = addEventListenerMock.mock.calls[0]; - - wrapper.destroy(); - - expect(removeEventListenerMock).toHaveBeenCalledWith('popstate', fn); - }); - - it('emits popstate event when popstate is triggered', async () => { - const [, fn] = addEventListenerMock.mock.calls[0]; - - delete window.location; - window.location = new URL(`${TEST_HOST}/?page=5`); - fn(); - - expect(wrapper.emitted().popstate[0]).toStrictEqual([5]); - }); -}); diff --git a/spec/frontend/import_projects/components/provider_repo_table_row_spec.js b/spec/frontend/import_projects/components/provider_repo_table_row_spec.js index bd9cd07db78..03e30ef610e 100644 --- a/spec/frontend/import_projects/components/provider_repo_table_row_spec.js +++ b/spec/frontend/import_projects/components/provider_repo_table_row_spec.js @@ -1,6 +1,7 @@ import { nextTick } from 'vue'; import Vuex from 'vuex'; import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { GlBadge } from '@gitlab/ui'; import ProviderRepoTableRow from '~/import_projects/components/provider_repo_table_row.vue'; import ImportStatus from '~/import_projects/components/import_status.vue'; import { STATUSES } from '~/import_projects/constants'; @@ -14,20 +15,6 @@ describe('ProviderRepoTableRow', () => { targetNamespace: 'target', newName: 'newName', }; - const ciCdOnly = false; - const repo = { - importSource: { - id: 'remote-1', - fullName: 'fullName', - providerLink: 'providerLink', - }, - importedProject: { - id: 1, - fullPath: 'fullPath', - importSource: 'importSource', - }, - importStatus: STATUSES.FINISHED, - }; const availableNamespaces = [ { text: 'Groups', children: [{ id: 'test', text: 'test' }] }, @@ -46,55 +33,137 @@ describe('ProviderRepoTableRow', () => { return store; } - const findImportButton = () => - wrapper - .findAll('button') - .filter(node => node.text() === 'Import') - .at(0); + const findImportButton = () => { + const buttons = wrapper.findAll('button').filter(node => node.text() === 'Import'); + + return buttons.length ? buttons.at(0) : buttons; + }; - function mountComponent(initialState) { + function mountComponent(props) { const localVue = createLocalVue(); localVue.use(Vuex); - const store = initStore({ ciCdOnly, ...initialState }); + const store = initStore(); wrapper = shallowMount(ProviderRepoTableRow, { localVue, store, - propsData: { repo, availableNamespaces }, + propsData: { availableNamespaces, ...props }, }); } - beforeEach(() => { - mountComponent(); - }); - afterEach(() => { wrapper.destroy(); + wrapper = null; }); - it('renders a provider repo table row', () => { - const providerLink = wrapper.find('[data-testid=providerLink]'); + describe('when rendering importable project', () => { + const repo = { + importSource: { + id: 'remote-1', + fullName: 'fullName', + providerLink: 'providerLink', + }, + }; + + beforeEach(() => { + mountComponent({ repo }); + }); + + it('renders project information', () => { + const providerLink = wrapper.find('[data-testid=providerLink]'); + + expect(providerLink.attributes().href).toMatch(repo.importSource.providerLink); + expect(providerLink.text()).toMatch(repo.importSource.fullName); + }); + + it('renders empty import status', () => { + expect(wrapper.find(ImportStatus).props().status).toBe(STATUSES.NONE); + }); + + it('renders a select2 namespace select', () => { + expect(wrapper.find(Select2Select).exists()).toBe(true); + expect(wrapper.find(Select2Select).props().options.data).toBe(availableNamespaces); + }); + + it('renders import button', () => { + expect(findImportButton().exists()).toBe(true); + }); + + it('imports repo when clicking import button', async () => { + findImportButton().trigger('click'); + + await nextTick(); + + const { calls } = fetchImport.mock; - expect(providerLink.attributes().href).toMatch(repo.importSource.providerLink); - expect(providerLink.text()).toMatch(repo.importSource.fullName); - expect(wrapper.find(ImportStatus).props().status).toBe(repo.importStatus); - expect(wrapper.contains('button')).toBe(true); + expect(calls).toHaveLength(1); + expect(calls[0][1]).toBe(repo.importSource.id); + }); }); - it('renders a select2 namespace select', () => { - expect(wrapper.contains(Select2Select)).toBe(true); - expect(wrapper.find(Select2Select).props().options.data).toBe(availableNamespaces); + describe('when rendering imported project', () => { + const repo = { + importSource: { + id: 'remote-1', + fullName: 'fullName', + providerLink: 'providerLink', + }, + importedProject: { + id: 1, + fullPath: 'fullPath', + importSource: 'importSource', + importStatus: STATUSES.FINISHED, + }, + }; + + beforeEach(() => { + mountComponent({ repo }); + }); + + it('renders project information', () => { + const providerLink = wrapper.find('[data-testid=providerLink]'); + + expect(providerLink.attributes().href).toMatch(repo.importSource.providerLink); + expect(providerLink.text()).toMatch(repo.importSource.fullName); + }); + + it('renders proper import status', () => { + expect(wrapper.find(ImportStatus).props().status).toBe(repo.importedProject.importStatus); + }); + + it('does not renders a namespace select', () => { + expect(wrapper.find(Select2Select).exists()).toBe(false); + }); + + it('does not render import button', () => { + expect(findImportButton().exists()).toBe(false); + }); }); - it('imports repo when clicking import button', async () => { - findImportButton().trigger('click'); + describe('when rendering incompatible project', () => { + const repo = { + importSource: { + id: 'remote-1', + fullName: 'fullName', + providerLink: 'providerLink', + incompatible: true, + }, + }; - await nextTick(); + beforeEach(() => { + mountComponent({ repo }); + }); + + it('renders project information', () => { + const providerLink = wrapper.find('[data-testid=providerLink]'); - const { calls } = fetchImport.mock; + expect(providerLink.attributes().href).toMatch(repo.importSource.providerLink); + expect(providerLink.text()).toMatch(repo.importSource.fullName); + }); - expect(calls).toHaveLength(1); - expect(calls[0][1]).toBe(repo.importSource.id); + it('renders badge with error', () => { + expect(wrapper.find(GlBadge).text()).toBe('Incompatible project'); + }); }); }); diff --git a/spec/frontend/import_projects/store/actions_spec.js b/spec/frontend/import_projects/store/actions_spec.js index 45a59b3f6d6..6951f2bf04d 100644 --- a/spec/frontend/import_projects/store/actions_spec.js +++ b/spec/frontend/import_projects/store/actions_spec.js @@ -83,7 +83,7 @@ describe('import_projects store actions', () => { afterEach(() => mock.restore()); - it('dispatches stopJobsPolling actions and commits REQUEST_REPOS, RECEIVE_REPOS_SUCCESS mutations on a successful request', () => { + it('commits SET_PAGE, REQUEST_REPOS, RECEIVE_REPOS_SUCCESS mutations on a successful request', () => { mock.onGet(MOCK_ENDPOINT).reply(200, payload); return testAction( @@ -91,54 +91,65 @@ describe('import_projects store actions', () => { null, localState, [ + { type: SET_PAGE, payload: 1 }, { type: REQUEST_REPOS }, { type: RECEIVE_REPOS_SUCCESS, payload: convertObjectPropsToCamelCase(payload, { deep: true }), }, ], - [{ type: 'stopJobsPolling' }, { type: 'fetchJobs' }], + [], ); }); - it('dispatches stopJobsPolling action and commits REQUEST_REPOS, RECEIVE_REPOS_ERROR mutations on an unsuccessful request', () => { + it('commits SET_PAGE, REQUEST_REPOS, RECEIVE_REPOS_ERROR and SET_PAGE again mutations on an unsuccessful request', () => { mock.onGet(MOCK_ENDPOINT).reply(500); return testAction( fetchRepos, null, localState, - [{ type: REQUEST_REPOS }, { type: RECEIVE_REPOS_ERROR }], - [{ type: 'stopJobsPolling' }], + [ + { type: SET_PAGE, payload: 1 }, + { type: REQUEST_REPOS }, + { type: SET_PAGE, payload: 0 }, + { type: RECEIVE_REPOS_ERROR }, + ], + [], ); }); - describe('when pagination is enabled', () => { - it('includes page in url query params', async () => { - const { fetchRepos: fetchReposWithPagination } = actionsFactory({ - endpoints, - hasPagination: true, - }); + it('includes page in url query params', async () => { + let requestedUrl; + mock.onGet().reply(config => { + requestedUrl = config.url; + return [200, payload]; + }); - let requestedUrl; - mock.onGet().reply(config => { - requestedUrl = config.url; - return [200, payload]; - }); + const localStateWithPage = { ...localState, pageInfo: { page: 2 } }; - await testAction( - fetchReposWithPagination, - null, - localState, - expect.any(Array), - expect.any(Array), - ); + await testAction(fetchRepos, null, localStateWithPage, expect.any(Array), expect.any(Array)); - expect(requestedUrl).toBe(`${MOCK_ENDPOINT}?page=${localState.pageInfo.page}`); - }); + expect(requestedUrl).toBe(`${MOCK_ENDPOINT}?page=${localStateWithPage.pageInfo.page + 1}`); }); - describe('when filtered', () => { + it('correctly updates current page on an unsuccessful request', () => { + mock.onGet(MOCK_ENDPOINT).reply(500); + const CURRENT_PAGE = 5; + + return testAction( + fetchRepos, + null, + { ...localState, pageInfo: { page: CURRENT_PAGE } }, + expect.arrayContaining([ + { type: SET_PAGE, payload: CURRENT_PAGE + 1 }, + { type: SET_PAGE, payload: CURRENT_PAGE }, + ]), + [], + ); + }); + + describe('when /home/xanf/projects/gdk/gitlab/spec/frontend/import_projects/store/actions_spec.jsfiltered', () => { it('fetches repos with filter applied', () => { mock.onGet(`${TEST_HOST}/endpoint.json?filter=filter`).reply(200, payload); @@ -147,13 +158,14 @@ describe('import_projects store actions', () => { null, { ...localState, filter: 'filter' }, [ + { type: SET_PAGE, payload: 1 }, { type: REQUEST_REPOS }, { type: RECEIVE_REPOS_SUCCESS, payload: convertObjectPropsToCamelCase(payload, { deep: true }), }, ], - [{ type: 'stopJobsPolling' }, { type: 'fetchJobs' }], + [], ); }); }); diff --git a/spec/frontend/import_projects/store/getters_spec.js b/spec/frontend/import_projects/store/getters_spec.js index 5c1ea25a684..1ce42e534ea 100644 --- a/spec/frontend/import_projects/store/getters_spec.js +++ b/spec/frontend/import_projects/store/getters_spec.js @@ -3,6 +3,7 @@ import { isImportingAnyRepo, hasIncompatibleRepos, hasImportableRepos, + importAllCount, getImportTarget, } from '~/import_projects/store/getters'; import { STATUSES } from '~/import_projects/constants'; @@ -10,13 +11,12 @@ import state from '~/import_projects/store/state'; const IMPORTED_REPO = { importSource: {}, - importedProject: { fullPath: 'some/path' }, + importedProject: { fullPath: 'some/path', importStatus: STATUSES.FINISHED }, }; const IMPORTABLE_REPO = { importSource: { id: 'some-id', sanitizedName: 'sanitized' }, importedProject: null, - importStatus: STATUSES.NONE, }; const INCOMPATIBLE_REPO = { @@ -56,14 +56,20 @@ describe('import_projects store getters', () => { ${STATUSES.STARTED} | ${true} ${STATUSES.FINISHED} | ${false} `( - 'isImportingAnyRepo returns $value when repo with $importStatus status is available', + 'isImportingAnyRepo returns $value when project with $importStatus status is available', ({ importStatus, value }) => { - localState.repositories = [{ importStatus }]; + localState.repositories = [{ importedProject: { importStatus } }]; expect(isImportingAnyRepo(localState)).toBe(value); }, ); + it('isImportingAnyRepo returns false when project with no defined importStatus status is available', () => { + localState.repositories = [{ importSource: {} }]; + + expect(isImportingAnyRepo(localState)).toBe(false); + }); + describe('hasIncompatibleRepos', () => { it('returns true if there are any incompatible projects', () => { localState.repositories = [IMPORTABLE_REPO, IMPORTED_REPO, INCOMPATIBLE_REPO]; @@ -92,6 +98,19 @@ describe('import_projects store getters', () => { }); }); + describe('importAllCount', () => { + it('returns count of available importable projects ', () => { + localState.repositories = [ + IMPORTABLE_REPO, + IMPORTABLE_REPO, + IMPORTED_REPO, + INCOMPATIBLE_REPO, + ]; + + expect(importAllCount(localState)).toBe(2); + }); + }); + describe('getImportTarget', () => { it('returns default value if no custom target available', () => { localState.defaultTargetNamespace = 'default'; diff --git a/spec/frontend/import_projects/store/mutations_spec.js b/spec/frontend/import_projects/store/mutations_spec.js index 3672ec9f2c0..5d78a7fa9e7 100644 --- a/spec/frontend/import_projects/store/mutations_spec.js +++ b/spec/frontend/import_projects/store/mutations_spec.js @@ -1,9 +1,11 @@ import * as types from '~/import_projects/store/mutation_types'; import mutations from '~/import_projects/store/mutations'; +import getInitialState from '~/import_projects/store/state'; import { STATUSES } from '~/import_projects/constants'; describe('import_projects store mutations', () => { let state; + const SOURCE_PROJECT = { id: 1, full_name: 'full/name', @@ -19,13 +21,23 @@ describe('import_projects store mutations', () => { }; describe(`${types.SET_FILTER}`, () => { - it('overwrites current filter value', () => { - state = { filter: 'some-value' }; - const NEW_VALUE = 'new-value'; + const NEW_VALUE = 'new-value'; + beforeEach(() => { + state = { + filter: 'some-value', + repositories: ['some', ' repositories'], + pageInfo: { page: 1 }, + }; mutations[types.SET_FILTER](state, NEW_VALUE); + }); - expect(state.filter).toBe(NEW_VALUE); + it('removes current repositories list', () => { + expect(state.repositories.length).toBe(0); + }); + + it('resets current page to 0', () => { + expect(state.pageInfo.page).toBe(0); }); }); @@ -40,93 +52,104 @@ describe('import_projects store mutations', () => { }); describe(`${types.RECEIVE_REPOS_SUCCESS}`, () => { - describe('for imported projects', () => { - const response = { - importedProjects: [IMPORTED_PROJECT], - providerRepos: [], - }; + describe('with legacy response format', () => { + describe('for imported projects', () => { + const response = { + importedProjects: [IMPORTED_PROJECT], + providerRepos: [], + }; - it('picks import status from response', () => { - state = {}; + it('recreates importSource from response', () => { + state = getInitialState(); - mutations[types.RECEIVE_REPOS_SUCCESS](state, response); + mutations[types.RECEIVE_REPOS_SUCCESS](state, response); - expect(state.repositories[0].importStatus).toBe(IMPORTED_PROJECT.importStatus); - }); + expect(state.repositories[0].importSource).toStrictEqual( + expect.objectContaining({ + fullName: IMPORTED_PROJECT.importSource, + sanitizedName: IMPORTED_PROJECT.name, + providerLink: IMPORTED_PROJECT.providerLink, + }), + ); + }); - it('recreates importSource from response', () => { - state = {}; + it('passes project to importProject', () => { + state = getInitialState(); - mutations[types.RECEIVE_REPOS_SUCCESS](state, response); + mutations[types.RECEIVE_REPOS_SUCCESS](state, response); - expect(state.repositories[0].importSource).toStrictEqual( - expect.objectContaining({ - fullName: IMPORTED_PROJECT.importSource, - sanitizedName: IMPORTED_PROJECT.name, - providerLink: IMPORTED_PROJECT.providerLink, - }), - ); + expect(IMPORTED_PROJECT).toStrictEqual( + expect.objectContaining(state.repositories[0].importedProject), + ); + }); }); - it('passes project to importProject', () => { - state = {}; + describe('for importable projects', () => { + beforeEach(() => { + state = getInitialState(); - mutations[types.RECEIVE_REPOS_SUCCESS](state, response); + const response = { + importedProjects: [], + providerRepos: [SOURCE_PROJECT], + }; + mutations[types.RECEIVE_REPOS_SUCCESS](state, response); + }); - expect(IMPORTED_PROJECT).toStrictEqual( - expect.objectContaining(state.repositories[0].importedProject), - ); + it('sets importSource to project', () => { + expect(state.repositories[0].importSource).toBe(SOURCE_PROJECT); + }); }); - }); - describe('for importable projects', () => { - beforeEach(() => { - state = {}; + describe('for incompatible projects', () => { const response = { importedProjects: [], - providerRepos: [SOURCE_PROJECT], + providerRepos: [], + incompatibleRepos: [SOURCE_PROJECT], }; - mutations[types.RECEIVE_REPOS_SUCCESS](state, response); - }); - it('sets import status to none', () => { - expect(state.repositories[0].importStatus).toBe(STATUSES.NONE); - }); + beforeEach(() => { + state = getInitialState(); + mutations[types.RECEIVE_REPOS_SUCCESS](state, response); + }); + + it('sets incompatible flag', () => { + expect(state.repositories[0].importSource.incompatible).toBe(true); + }); - it('sets importSource to project', () => { - expect(state.repositories[0].importSource).toBe(SOURCE_PROJECT); + it('sets importSource to project', () => { + expect(state.repositories[0].importSource).toStrictEqual( + expect.objectContaining(SOURCE_PROJECT), + ); + }); }); - }); - describe('for incompatible projects', () => { - const response = { - importedProjects: [], - providerRepos: [], - incompatibleRepos: [SOURCE_PROJECT], - }; + it('sets repos loading flag to false', () => { + const response = { + importedProjects: [], + providerRepos: [], + }; + + state = getInitialState(); - beforeEach(() => { - state = {}; mutations[types.RECEIVE_REPOS_SUCCESS](state, response); - }); - it('sets incompatible flag', () => { - expect(state.repositories[0].importSource.incompatible).toBe(true); + expect(state.isLoadingRepos).toBe(false); }); + }); - it('sets importSource to project', () => { - expect(state.repositories[0].importSource).toStrictEqual( - expect.objectContaining(SOURCE_PROJECT), - ); - }); + it('passes response as it is', () => { + const response = []; + state = getInitialState(); + + mutations[types.RECEIVE_REPOS_SUCCESS](state, response); + + expect(state.repositories).toStrictEqual(response); }); it('sets repos loading flag to false', () => { - const response = { - importedProjects: [], - providerRepos: [], - }; - state = {}; + const response = []; + + state = getInitialState(); mutations[types.RECEIVE_REPOS_SUCCESS](state, response); @@ -136,7 +159,7 @@ describe('import_projects store mutations', () => { describe(`${types.RECEIVE_REPOS_ERROR}`, () => { it('sets repos loading flag to false', () => { - state = {}; + state = getInitialState(); mutations[types.RECEIVE_REPOS_ERROR](state); @@ -154,7 +177,7 @@ describe('import_projects store mutations', () => { }); it(`sets status to ${STATUSES.SCHEDULING}`, () => { - expect(state.repositories[0].importStatus).toBe(STATUSES.SCHEDULING); + expect(state.repositories[0].importedProject.importStatus).toBe(STATUSES.SCHEDULING); }); }); @@ -170,7 +193,9 @@ describe('import_projects store mutations', () => { }); it('sets import status', () => { - expect(state.repositories[0].importStatus).toBe(IMPORTED_PROJECT.importStatus); + expect(state.repositories[0].importedProject.importStatus).toBe( + IMPORTED_PROJECT.importStatus, + ); }); it('sets imported project', () => { @@ -188,8 +213,8 @@ describe('import_projects store mutations', () => { mutations[types.RECEIVE_IMPORT_ERROR](state, REPO_ID); }); - it(`resets import status to ${STATUSES.NONE}`, () => { - expect(state.repositories[0].importStatus).toBe(STATUSES.NONE); + it(`removes importedProject entry`, () => { + expect(state.repositories[0].importedProject).toBeNull(); }); }); @@ -203,7 +228,9 @@ describe('import_projects store mutations', () => { mutations[types.RECEIVE_JOBS_SUCCESS](state, updatedProjects); - expect(state.repositories[0].importStatus).toBe(updatedProjects[0].importStatus); + expect(state.repositories[0].importedProject.importStatus).toBe( + updatedProjects[0].importStatus, + ); }); }); @@ -280,17 +307,6 @@ describe('import_projects store mutations', () => { }); }); - describe(`${types.SET_PAGE_INFO}`, () => { - it('sets passed page info', () => { - state = {}; - const pageInfo = { page: 1, total: 10 }; - - mutations[types.SET_PAGE_INFO](state, pageInfo); - - expect(state.pageInfo).toBe(pageInfo); - }); - }); - describe(`${types.SET_PAGE}`, () => { it('sets page number', () => { const NEW_PAGE = 4; diff --git a/spec/frontend/import_projects/utils_spec.js b/spec/frontend/import_projects/utils_spec.js index 826b06d5a70..4e1e16a3184 100644 --- a/spec/frontend/import_projects/utils_spec.js +++ b/spec/frontend/import_projects/utils_spec.js @@ -1,7 +1,16 @@ -import { isProjectImportable } from '~/import_projects/utils'; +import { isProjectImportable, isIncompatible, getImportStatus } from '~/import_projects/utils'; import { STATUSES } from '~/import_projects/constants'; describe('import_projects utils', () => { + const COMPATIBLE_PROJECT = { + importSource: { incompatible: false }, + }; + + const INCOMPATIBLE_PROJECT = { + importSource: { incompatible: true }, + importedProject: null, + }; + describe('isProjectImportable', () => { it.each` status | result @@ -14,19 +23,43 @@ describe('import_projects utils', () => { `('returns $result when project is compatible and status is $status', ({ status, result }) => { expect( isProjectImportable({ - importStatus: status, - importSource: { incompatible: false }, + ...COMPATIBLE_PROJECT, + importedProject: { importStatus: status }, }), ).toBe(result); }); + it('returns true if import status is not defined', () => { + expect(isProjectImportable({ importSource: {} })).toBe(true); + }); + it('returns false if project is not compatible', () => { + expect(isProjectImportable(INCOMPATIBLE_PROJECT)).toBe(false); + }); + }); + + describe('isIncompatible', () => { + it('returns true for incompatible project', () => { + expect(isIncompatible(INCOMPATIBLE_PROJECT)).toBe(true); + }); + + it('returns false for compatible project', () => { + expect(isIncompatible(COMPATIBLE_PROJECT)).toBe(false); + }); + }); + + describe('getImportStatus', () => { + it('returns actual status when project status is provided', () => { expect( - isProjectImportable({ - importStatus: STATUSES.NONE, - importSource: { incompatible: true }, + getImportStatus({ + ...COMPATIBLE_PROJECT, + importedProject: { importStatus: STATUSES.FINISHED }, }), - ).toBe(false); + ).toBe(STATUSES.FINISHED); + }); + + it('returns NONE as status if import status is not provided', () => { + expect(getImportStatus(COMPATIBLE_PROJECT)).toBe(STATUSES.NONE); }); }); }); |