diff options
Diffstat (limited to 'spec/frontend/import_projects/components')
5 files changed, 179 insertions, 219 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'); + }); }); }); |