diff options
-rw-r--r-- | app/assets/javascripts/import_projects/components/import_projects_table.vue | 2 | ||||
-rw-r--r-- | app/assets/javascripts/import_projects/store/index.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/vue_shared/components/select2_select.vue | 2 | ||||
-rw-r--r-- | spec/frontend/import_projects/components/import_projects_table_spec.js | 185 | ||||
-rw-r--r-- | spec/frontend/import_projects/components/imported_project_table_row_spec.js (renamed from spec/javascripts/import_projects/components/imported_project_table_row_spec.js) | 25 | ||||
-rw-r--r-- | spec/frontend/import_projects/components/provider_repo_table_row_spec.js (renamed from spec/javascripts/import_projects/components/provider_repo_table_row_spec.js) | 90 | ||||
-rw-r--r-- | spec/frontend/import_projects/store/actions_spec.js (renamed from spec/javascripts/import_projects/store/actions_spec.js) | 4 | ||||
-rw-r--r-- | spec/javascripts/import_projects/components/import_projects_table_spec.js | 188 |
8 files changed, 256 insertions, 242 deletions
diff --git a/app/assets/javascripts/import_projects/components/import_projects_table.vue b/app/assets/javascripts/import_projects/components/import_projects_table.vue index 777f8fa6691..00eb0afb3bf 100644 --- a/app/assets/javascripts/import_projects/components/import_projects_table.vue +++ b/app/assets/javascripts/import_projects/components/import_projects_table.vue @@ -74,7 +74,7 @@ export default { <gl-loading-icon v-if="isLoadingRepos" class="js-loading-button-icon import-projects-loading-icon" - :size="4" + size="md" /> <div v-else-if="hasProviderRepos || hasImportedProjects" class="table-responsive"> <table class="table import-table"> diff --git a/app/assets/javascripts/import_projects/store/index.js b/app/assets/javascripts/import_projects/store/index.js index f666e2ebf33..ff1fd1e598e 100644 --- a/app/assets/javascripts/import_projects/store/index.js +++ b/app/assets/javascripts/import_projects/store/index.js @@ -7,6 +7,8 @@ import mutations from './mutations'; Vue.use(Vuex); +export { state, actions, getters, mutations }; + export default () => new Vuex.Store({ state: state(), diff --git a/app/assets/javascripts/vue_shared/components/select2_select.vue b/app/assets/javascripts/vue_shared/components/select2_select.vue index 3074ea859cc..6d2612556ff 100644 --- a/app/assets/javascripts/vue_shared/components/select2_select.vue +++ b/app/assets/javascripts/vue_shared/components/select2_select.vue @@ -1,6 +1,6 @@ <script> import $ from 'jquery'; -import 'select2/select2'; +import 'select2'; export default { name: 'Select2Select', diff --git a/spec/frontend/import_projects/components/import_projects_table_spec.js b/spec/frontend/import_projects/components/import_projects_table_spec.js new file mode 100644 index 00000000000..17a998d0174 --- /dev/null +++ b/spec/frontend/import_projects/components/import_projects_table_spec.js @@ -0,0 +1,185 @@ +import Vuex from 'vuex'; +import { createLocalVue, mount } from '@vue/test-utils'; +import { state, actions, getters, mutations } from '~/import_projects/store'; +import importProjectsTable from '~/import_projects/components/import_projects_table.vue'; +import STATUS_MAP from '~/import_projects/constants'; + +describe('ImportProjectsTable', () => { + let vm; + const providerTitle = 'THE PROVIDER'; + const providerRepo = { id: 10, sanitizedName: 'sanitizedName', fullName: 'fullName' }; + const importedProject = { + id: 1, + fullPath: 'fullPath', + importStatus: 'started', + providerLink: 'providerLink', + importSource: 'importSource', + }; + + function initStore() { + const stubbedActions = Object.assign({}, actions, { + fetchJobs: jest.fn(), + fetchRepos: jest.fn(actions.requestRepos), + fetchImport: jest.fn(actions.requestImport), + }); + + const store = new Vuex.Store({ + state: state(), + actions: stubbedActions, + mutations, + getters, + }); + + return store; + } + + function mountComponent() { + const localVue = createLocalVue(); + localVue.use(Vuex); + + const store = initStore(); + + const component = mount(importProjectsTable, { + localVue, + store, + propsData: { + providerTitle, + }, + sync: false, + }); + + return component.vm; + } + + beforeEach(() => { + vm = mountComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders a loading icon whilst repos are loading', () => + vm.$nextTick().then(() => { + expect(vm.$el.querySelector('.js-loading-button-icon')).not.toBeNull(); + })); + + it('renders a table with imported projects and provider repos', () => { + vm.$store.dispatch('receiveReposSuccess', { + importedProjects: [importedProject], + providerRepos: [providerRepo], + namespaces: [{ path: 'path' }], + }); + + return vm.$nextTick().then(() => { + expect(vm.$el.querySelector('.js-loading-button-icon')).toBeNull(); + expect(vm.$el.querySelector('.table')).not.toBeNull(); + expect(vm.$el.querySelector('.import-jobs-from-col').innerText).toMatch( + `From ${providerTitle}`, + ); + + expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull(); + expect(vm.$el.querySelector('.js-provider-repo')).not.toBeNull(); + }); + }); + + it('renders an empty state if there are no imported projects or provider repos', () => { + vm.$store.dispatch('receiveReposSuccess', { + importedProjects: [], + providerRepos: [], + namespaces: [], + }); + + return vm.$nextTick().then(() => { + expect(vm.$el.querySelector('.js-loading-button-icon')).toBeNull(); + expect(vm.$el.querySelector('.table')).toBeNull(); + expect(vm.$el.innerText).toMatch(`No ${providerTitle} repositories available to import`); + }); + }); + + it('shows loading spinner when bulk import button is clicked', () => { + vm.$store.dispatch('receiveReposSuccess', { + importedProjects: [], + providerRepos: [providerRepo], + namespaces: [{ path: 'path' }], + }); + + return vm + .$nextTick() + .then(() => { + expect(vm.$el.querySelector('.js-imported-project')).toBeNull(); + expect(vm.$el.querySelector('.js-provider-repo')).not.toBeNull(); + + vm.$el.querySelector('.js-import-all').click(); + }) + .then(() => vm.$nextTick()) + .then(() => { + expect(vm.$el.querySelector('.js-import-all .js-loading-button-icon')).not.toBeNull(); + }); + }); + + it('imports provider repos if bulk import button is clicked', () => { + mountComponent(); + + vm.$store.dispatch('receiveReposSuccess', { + importedProjects: [], + providerRepos: [providerRepo], + namespaces: [{ path: 'path' }], + }); + + return vm + .$nextTick() + .then(() => { + expect(vm.$el.querySelector('.js-imported-project')).toBeNull(); + expect(vm.$el.querySelector('.js-provider-repo')).not.toBeNull(); + + vm.$store.dispatch('receiveImportSuccess', { importedProject, repoId: providerRepo.id }); + }) + .then(() => vm.$nextTick()) + .then(() => { + expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull(); + expect(vm.$el.querySelector('.js-provider-repo')).toBeNull(); + }); + }); + + it('polls to update the status of imported projects', () => { + const updatedProjects = [ + { + id: importedProject.id, + importStatus: 'finished', + }, + ]; + + vm.$store.dispatch('receiveReposSuccess', { + importedProjects: [importedProject], + providerRepos: [], + namespaces: [{ path: 'path' }], + }); + + return vm + .$nextTick() + .then(() => { + const statusObject = STATUS_MAP[importedProject.importStatus]; + + expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull(); + expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch( + statusObject.text, + ); + + expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull(); + + vm.$store.dispatch('receiveJobsSuccess', updatedProjects); + }) + .then(() => vm.$nextTick()) + .then(() => { + const statusObject = STATUS_MAP[updatedProjects[0].importStatus]; + + expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull(); + expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch( + statusObject.text, + ); + + expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull(); + }); + }); +}); diff --git a/spec/javascripts/import_projects/components/imported_project_table_row_spec.js b/spec/frontend/import_projects/components/imported_project_table_row_spec.js index 7dac7e9ccc1..f95acc1edd7 100644 --- a/spec/javascripts/import_projects/components/imported_project_table_row_spec.js +++ b/spec/frontend/import_projects/components/imported_project_table_row_spec.js @@ -1,5 +1,6 @@ -import Vue from 'vue'; +import Vuex from 'vuex'; import createStore from '~/import_projects/store'; +import { createLocalVue, mount } from '@vue/test-utils'; import importedProjectTableRow from '~/import_projects/components/imported_project_table_row.vue'; import STATUS_MAP from '~/import_projects/constants'; @@ -13,27 +14,33 @@ describe('ImportedProjectTableRow', () => { importSource: 'importSource', }; - function createComponent() { - const ImportedProjectTableRow = Vue.extend(importedProjectTableRow); + function mountComponent() { + const localVue = createLocalVue(); + localVue.use(Vuex); - const store = createStore(); - return new ImportedProjectTableRow({ - store, + const component = mount(importedProjectTableRow, { + localVue, + store: createStore(), propsData: { project: { ...project, }, }, - }).$mount(); + sync: false, + }); + + return component.vm; } + beforeEach(() => { + vm = mountComponent(); + }); + afterEach(() => { vm.$destroy(); }); it('renders an imported project table row', () => { - vm = createComponent(); - const providerLink = vm.$el.querySelector('.js-provider-link'); const statusObject = STATUS_MAP[project.importStatus]; diff --git a/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js b/spec/frontend/import_projects/components/provider_repo_table_row_spec.js index 4d2bacd2ad0..02c786d8d0b 100644 --- a/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js +++ b/spec/frontend/import_projects/components/provider_repo_table_row_spec.js @@ -1,14 +1,15 @@ -import Vue from 'vue'; -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import createStore from '~/import_projects/store'; +import Vuex from 'vuex'; +import { createLocalVue, mount } from '@vue/test-utils'; +import { state, actions, getters, mutations } from '~/import_projects/store'; import providerRepoTableRow from '~/import_projects/components/provider_repo_table_row.vue'; import STATUS_MAP, { STATUSES } from '~/import_projects/constants'; -import setTimeoutPromise from '../../helpers/set_timeout_promise_helper'; describe('ProviderRepoTableRow', () => { - let store; let vm; + const fetchImport = jest.fn((context, data) => actions.requestImport(context, data)); + const importPath = '/import-path'; + const defaultTargetNamespace = 'user'; + const ciCdOnly = true; const repo = { id: 10, sanitizedName: 'sanitizedName', @@ -16,21 +17,42 @@ describe('ProviderRepoTableRow', () => { providerLink: 'providerLink', }; - function createComponent() { - const ProviderRepoTableRow = Vue.extend(providerRepoTableRow); + function initStore() { + const stubbedActions = Object.assign({}, actions, { + fetchImport, + }); - return new ProviderRepoTableRow({ + const store = new Vuex.Store({ + state: state(), + actions: stubbedActions, + mutations, + getters, + }); + + return store; + } + + function mountComponent() { + const localVue = createLocalVue(); + localVue.use(Vuex); + + const store = initStore(); + store.dispatch('setInitialData', { importPath, defaultTargetNamespace, ciCdOnly }); + + const component = mount(providerRepoTableRow, { + localVue, store, propsData: { - repo: { - ...repo, - }, + repo, }, - }).$mount(); + sync: false, + }); + + return component.vm; } beforeEach(() => { - store = createStore(); + vm = mountComponent(); }); afterEach(() => { @@ -38,8 +60,6 @@ describe('ProviderRepoTableRow', () => { }); it('renders a provider repo table row', () => { - vm = createComponent(); - const providerLink = vm.$el.querySelector('.js-provider-link'); const statusObject = STATUS_MAP[STATUSES.NONE]; @@ -55,8 +75,6 @@ describe('ProviderRepoTableRow', () => { }); it('renders a select2 namespace select', () => { - vm = createComponent(); - const dropdownTrigger = vm.$el.querySelector('.js-namespace-select'); expect(dropdownTrigger).not.toBeNull(); @@ -67,30 +85,20 @@ describe('ProviderRepoTableRow', () => { expect(vm.$el.querySelector('.select2-drop')).not.toBeNull(); }); - it('imports repo when clicking import button', done => { - const importPath = '/import-path'; - const defaultTargetNamespace = 'user'; - const ciCdOnly = true; - const mock = new MockAdapter(axios); - - store.dispatch('setInitialData', { importPath, defaultTargetNamespace, ciCdOnly }); - mock.onPost(importPath).replyOnce(200); - spyOn(store, 'dispatch').and.returnValue(new Promise(() => {})); - - vm = createComponent(); - + it('imports repo when clicking import button', () => { vm.$el.querySelector('.js-import-button').click(); - setTimeoutPromise() - .then(() => { - expect(store.dispatch).toHaveBeenCalledWith('fetchImport', { - repo, - newName: repo.sanitizedName, - targetNamespace: defaultTargetNamespace, - }); - }) - .then(() => mock.restore()) - .then(done) - .catch(done.fail); + return vm.$nextTick().then(() => { + const { calls } = fetchImport.mock; + + // Not using .toBeCalledWith because it expects + // an unmatchable and undefined 3rd argument. + expect(calls.length).toBe(1); + expect(calls[0][1]).toEqual({ + repo, + newName: repo.sanitizedName, + targetNamespace: defaultTargetNamespace, + }); + }); }); }); diff --git a/spec/javascripts/import_projects/store/actions_spec.js b/spec/frontend/import_projects/store/actions_spec.js index 77850ee3283..6a7b90788dd 100644 --- a/spec/javascripts/import_projects/store/actions_spec.js +++ b/spec/frontend/import_projects/store/actions_spec.js @@ -27,8 +27,8 @@ import { stopJobsPolling, } from '~/import_projects/store/actions'; import state from '~/import_projects/store/state'; -import testAction from 'spec/helpers/vuex_action_helper'; -import { TEST_HOST } from 'spec/test_constants'; +import testAction from 'helpers/vuex_action_helper'; +import { TEST_HOST } from 'helpers/test_constants'; describe('import_projects store actions', () => { let localState; diff --git a/spec/javascripts/import_projects/components/import_projects_table_spec.js b/spec/javascripts/import_projects/components/import_projects_table_spec.js deleted file mode 100644 index ab8642bf0dd..00000000000 --- a/spec/javascripts/import_projects/components/import_projects_table_spec.js +++ /dev/null @@ -1,188 +0,0 @@ -import Vue from 'vue'; -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import createStore from '~/import_projects/store'; -import importProjectsTable from '~/import_projects/components/import_projects_table.vue'; -import STATUS_MAP from '~/import_projects/constants'; -import setTimeoutPromise from '../../helpers/set_timeout_promise_helper'; - -describe('ImportProjectsTable', () => { - let vm; - let mock; - let store; - const reposPath = '/repos-path'; - const jobsPath = '/jobs-path'; - const providerTitle = 'THE PROVIDER'; - const providerRepo = { id: 10, sanitizedName: 'sanitizedName', fullName: 'fullName' }; - const importedProject = { - id: 1, - fullPath: 'fullPath', - importStatus: 'started', - providerLink: 'providerLink', - importSource: 'importSource', - }; - - function createComponent() { - const ImportProjectsTable = Vue.extend(importProjectsTable); - - const component = new ImportProjectsTable({ - store, - propsData: { - providerTitle, - }, - }).$mount(); - - store.dispatch('stopJobsPolling'); - - return component; - } - - beforeEach(() => { - store = createStore(); - store.dispatch('setInitialData', { reposPath }); - mock = new MockAdapter(axios); - }); - - afterEach(() => { - vm.$destroy(); - mock.restore(); - }); - - it('renders a loading icon whilst repos are loading', done => { - mock.restore(); // Stop the mock adapter from responding to the request, keeping the spinner up - - vm = createComponent(); - - setTimeoutPromise() - .then(() => { - expect(vm.$el.querySelector('.js-loading-button-icon')).not.toBeNull(); - }) - .then(() => done()) - .catch(() => done.fail()); - }); - - it('renders a table with imported projects and provider repos', done => { - const response = { - importedProjects: [importedProject], - providerRepos: [providerRepo], - namespaces: [{ path: 'path' }], - }; - mock.onGet(reposPath).reply(200, response); - - vm = createComponent(); - - setTimeoutPromise() - .then(() => { - expect(vm.$el.querySelector('.js-loading-button-icon')).toBeNull(); - expect(vm.$el.querySelector('.table')).not.toBeNull(); - expect(vm.$el.querySelector('.import-jobs-from-col').innerText).toMatch( - `From ${providerTitle}`, - ); - - expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull(); - expect(vm.$el.querySelector('.js-provider-repo')).not.toBeNull(); - }) - .then(() => done()) - .catch(() => done.fail()); - }); - - it('renders an empty state if there are no imported projects or provider repos', done => { - const response = { - importedProjects: [], - providerRepos: [], - namespaces: [], - }; - mock.onGet(reposPath).reply(200, response); - - vm = createComponent(); - - setTimeoutPromise() - .then(() => { - expect(vm.$el.querySelector('.js-loading-button-icon')).toBeNull(); - expect(vm.$el.querySelector('.table')).toBeNull(); - expect(vm.$el.innerText).toMatch(`No ${providerTitle} repositories available to import`); - }) - .then(() => done()) - .catch(() => done.fail()); - }); - - it('imports provider repos if bulk import button is clicked', done => { - const importPath = '/import-path'; - const response = { - importedProjects: [], - providerRepos: [providerRepo], - namespaces: [{ path: 'path' }], - }; - - mock.onGet(reposPath).replyOnce(200, response); - mock.onPost(importPath).replyOnce(200, importedProject); - - store.dispatch('setInitialData', { importPath }); - - vm = createComponent(); - - setTimeoutPromise() - .then(() => { - expect(vm.$el.querySelector('.js-imported-project')).toBeNull(); - expect(vm.$el.querySelector('.js-provider-repo')).not.toBeNull(); - - vm.$el.querySelector('.js-import-all').click(); - }) - .then(() => setTimeoutPromise()) - .then(() => { - expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull(); - expect(vm.$el.querySelector('.js-provider-repo')).toBeNull(); - }) - .then(() => done()) - .catch(() => done.fail()); - }); - - it('polls to update the status of imported projects', done => { - const importPath = '/import-path'; - const response = { - importedProjects: [importedProject], - providerRepos: [], - namespaces: [{ path: 'path' }], - }; - const updatedProjects = [ - { - id: importedProject.id, - importStatus: 'finished', - }, - ]; - - mock.onGet(reposPath).replyOnce(200, response); - - store.dispatch('setInitialData', { importPath, jobsPath }); - - vm = createComponent(); - - setTimeoutPromise() - .then(() => { - const statusObject = STATUS_MAP[importedProject.importStatus]; - - expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull(); - expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch( - statusObject.text, - ); - - expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull(); - - mock.onGet(jobsPath).replyOnce(200, updatedProjects); - return store.dispatch('restartJobsPolling'); - }) - .then(() => setTimeoutPromise()) - .then(() => { - const statusObject = STATUS_MAP[updatedProjects[0].importStatus]; - - expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull(); - expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch( - statusObject.text, - ); - - expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull(); - }) - .then(() => done()) - .catch(() => done.fail()); - }); -}); |