diff options
author | João Cunha <j.a.cunha@gmail.com> | 2019-02-26 10:33:32 +0000 |
---|---|---|
committer | João Cunha <j.a.cunha@gmail.com> | 2019-02-26 10:33:32 +0000 |
commit | ed7065bbdb6f6fad3cc8fb369267fa811767c8a5 (patch) | |
tree | b3fda377c51504a6d21da1742cac3de7fd668ef0 /spec/javascripts/import_projects | |
parent | 26c7d04cd06d1126e4a4a7a1c9308d12de77172e (diff) | |
parent | 094d740886eaf62fd219dacd11aa9a966758a962 (diff) | |
download | gitlab-ce-56937-edit-knative-domain-after-it-has-been-deployed.tar.gz |
Merge branch 'master' into 56937-edit-knative-domain-after-it-has-been-deployed56937-edit-knative-domain-after-it-has-been-deployed
Diffstat (limited to 'spec/javascripts/import_projects')
6 files changed, 715 insertions, 0 deletions
diff --git a/spec/javascripts/import_projects/components/import_projects_table_spec.js b/spec/javascripts/import_projects/components/import_projects_table_spec.js new file mode 100644 index 00000000000..a1ff84ce259 --- /dev/null +++ b/spec/javascripts/import_projects/components/import_projects_table_spec.js @@ -0,0 +1,186 @@ +import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import store 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; + 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(); + + component.$store.dispatch('stopJobsPolling'); + + return component; + } + + beforeEach(() => { + 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 vm.$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()); + }); +}); diff --git a/spec/javascripts/import_projects/components/imported_project_table_row_spec.js b/spec/javascripts/import_projects/components/imported_project_table_row_spec.js new file mode 100644 index 00000000000..8af3b5954a9 --- /dev/null +++ b/spec/javascripts/import_projects/components/imported_project_table_row_spec.js @@ -0,0 +1,50 @@ +import Vue from 'vue'; +import store from '~/import_projects/store'; +import importedProjectTableRow from '~/import_projects/components/imported_project_table_row.vue'; +import STATUS_MAP from '~/import_projects/constants'; + +describe('ImportedProjectTableRow', () => { + let vm; + const project = { + id: 1, + fullPath: 'fullPath', + importStatus: 'finished', + providerLink: 'providerLink', + importSource: 'importSource', + }; + + function createComponent() { + const ImportedProjectTableRow = Vue.extend(importedProjectTableRow); + + return new ImportedProjectTableRow({ + store, + propsData: { + project: { + ...project, + }, + }, + }).$mount(); + } + + 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]; + + expect(vm.$el.classList.contains('js-imported-project')).toBe(true); + expect(providerLink.href).toMatch(project.providerLink); + expect(providerLink.textContent).toMatch(project.importSource); + expect(vm.$el.querySelector('.js-full-path').textContent).toMatch(project.fullPath); + expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch( + statusObject.text, + ); + + expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull(); + expect(vm.$el.querySelector('.js-go-to-project').href).toMatch(project.fullPath); + }); +}); diff --git a/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js b/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js new file mode 100644 index 00000000000..7191fc923ce --- /dev/null +++ b/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js @@ -0,0 +1,78 @@ +import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import store 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 vm; + const repo = { + id: 10, + sanitizedName: 'sanitizedName', + fullName: 'fullName', + providerLink: 'providerLink', + }; + + function createComponent() { + const ProviderRepoTableRow = Vue.extend(providerRepoTableRow); + + return new ProviderRepoTableRow({ + store, + propsData: { + repo: { + ...repo, + }, + }, + }).$mount(); + } + + afterEach(() => { + vm.$destroy(); + }); + + it('renders a provider repo table row', () => { + vm = createComponent(); + + const providerLink = vm.$el.querySelector('.js-provider-link'); + const statusObject = STATUS_MAP[STATUSES.NONE]; + + expect(vm.$el.classList.contains('js-provider-repo')).toBe(true); + expect(providerLink.href).toMatch(repo.providerLink); + expect(providerLink.textContent).toMatch(repo.fullName); + expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch( + statusObject.text, + ); + + expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull(); + expect(vm.$el.querySelector('.js-import-button')).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(); + + 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); + }); +}); diff --git a/spec/javascripts/import_projects/store/actions_spec.js b/spec/javascripts/import_projects/store/actions_spec.js new file mode 100644 index 00000000000..77850ee3283 --- /dev/null +++ b/spec/javascripts/import_projects/store/actions_spec.js @@ -0,0 +1,284 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { + SET_INITIAL_DATA, + REQUEST_REPOS, + RECEIVE_REPOS_SUCCESS, + RECEIVE_REPOS_ERROR, + REQUEST_IMPORT, + RECEIVE_IMPORT_SUCCESS, + RECEIVE_IMPORT_ERROR, + RECEIVE_JOBS_SUCCESS, +} from '~/import_projects/store/mutation_types'; +import { + setInitialData, + requestRepos, + receiveReposSuccess, + receiveReposError, + fetchRepos, + requestImport, + receiveImportSuccess, + receiveImportError, + fetchImport, + receiveJobsSuccess, + fetchJobs, + clearJobsEtagPoll, + 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'; + +describe('import_projects store actions', () => { + let localState; + const repoId = 1; + const repos = [{ id: 1 }, { id: 2 }]; + const importPayload = { newName: 'newName', targetNamespace: 'targetNamespace', repo: { id: 1 } }; + + beforeEach(() => { + localState = state(); + }); + + describe('setInitialData', () => { + it(`commits ${SET_INITIAL_DATA} mutation`, done => { + const initialData = { + reposPath: 'reposPath', + provider: 'provider', + jobsPath: 'jobsPath', + importPath: 'impapp/assets/javascripts/vue_shared/components/select2_select.vueortPath', + defaultTargetNamespace: 'defaultTargetNamespace', + ciCdOnly: 'ciCdOnly', + canSelectNamespace: 'canSelectNamespace', + }; + + testAction( + setInitialData, + initialData, + localState, + [{ type: SET_INITIAL_DATA, payload: initialData }], + [], + done, + ); + }); + }); + + describe('requestRepos', () => { + it(`requestRepos commits ${REQUEST_REPOS} mutation`, done => { + testAction( + requestRepos, + null, + localState, + [{ type: REQUEST_REPOS, payload: null }], + [], + done, + ); + }); + }); + + describe('receiveReposSuccess', () => { + it(`commits ${RECEIVE_REPOS_SUCCESS} mutation`, done => { + testAction( + receiveReposSuccess, + repos, + localState, + [{ type: RECEIVE_REPOS_SUCCESS, payload: repos }], + [], + done, + ); + }); + }); + + describe('receiveReposError', () => { + it(`commits ${RECEIVE_REPOS_ERROR} mutation`, done => { + testAction(receiveReposError, repos, localState, [{ type: RECEIVE_REPOS_ERROR }], [], done); + }); + }); + + describe('fetchRepos', () => { + let mock; + + beforeEach(() => { + localState.reposPath = `${TEST_HOST}/endpoint.json`; + mock = new MockAdapter(axios); + }); + + afterEach(() => mock.restore()); + + it('dispatches requestRepos and receiveReposSuccess actions on a successful request', done => { + const payload = { imported_projects: [{}], provider_repos: [{}], namespaces: [{}] }; + mock.onGet(`${TEST_HOST}/endpoint.json`).reply(200, payload); + + testAction( + fetchRepos, + null, + localState, + [], + [ + { type: 'requestRepos' }, + { + type: 'receiveReposSuccess', + payload: convertObjectPropsToCamelCase(payload, { deep: true }), + }, + { + type: 'fetchJobs', + }, + ], + done, + ); + }); + + it('dispatches requestRepos and receiveReposSuccess actions on an unsuccessful request', done => { + mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500); + + testAction( + fetchRepos, + null, + localState, + [], + [{ type: 'requestRepos' }, { type: 'receiveReposError' }], + done, + ); + }); + }); + + describe('requestImport', () => { + it(`commits ${REQUEST_IMPORT} mutation`, done => { + testAction( + requestImport, + repoId, + localState, + [{ type: REQUEST_IMPORT, payload: repoId }], + [], + done, + ); + }); + }); + + describe('receiveImportSuccess', () => { + it(`commits ${RECEIVE_IMPORT_SUCCESS} mutation`, done => { + const payload = { importedProject: { name: 'imported/project' }, repoId: 2 }; + + testAction( + receiveImportSuccess, + payload, + localState, + [{ type: RECEIVE_IMPORT_SUCCESS, payload }], + [], + done, + ); + }); + }); + + describe('receiveImportError', () => { + it(`commits ${RECEIVE_IMPORT_ERROR} mutation`, done => { + testAction( + receiveImportError, + repoId, + localState, + [{ type: RECEIVE_IMPORT_ERROR, payload: repoId }], + [], + done, + ); + }); + }); + + describe('fetchImport', () => { + let mock; + + beforeEach(() => { + localState.importPath = `${TEST_HOST}/endpoint.json`; + mock = new MockAdapter(axios); + }); + + afterEach(() => mock.restore()); + + it('dispatches requestImport and receiveImportSuccess actions on a successful request', done => { + const importedProject = { name: 'imported/project' }; + const importRepoId = importPayload.repo.id; + mock.onPost(`${TEST_HOST}/endpoint.json`).reply(200, importedProject); + + testAction( + fetchImport, + importPayload, + localState, + [], + [ + { type: 'requestImport', payload: importRepoId }, + { + type: 'receiveImportSuccess', + payload: { + importedProject: convertObjectPropsToCamelCase(importedProject, { deep: true }), + repoId: importRepoId, + }, + }, + ], + done, + ); + }); + + it('dispatches requestImport and receiveImportSuccess actions on an unsuccessful request', done => { + mock.onPost(`${TEST_HOST}/endpoint.json`).reply(500); + + testAction( + fetchImport, + importPayload, + localState, + [], + [ + { type: 'requestImport', payload: importPayload.repo.id }, + { type: 'receiveImportError', payload: { repoId: importPayload.repo.id } }, + ], + done, + ); + }); + }); + + describe('receiveJobsSuccess', () => { + it(`commits ${RECEIVE_JOBS_SUCCESS} mutation`, done => { + testAction( + receiveJobsSuccess, + repos, + localState, + [{ type: RECEIVE_JOBS_SUCCESS, payload: repos }], + [], + done, + ); + }); + }); + + describe('fetchJobs', () => { + let mock; + + beforeEach(() => { + localState.jobsPath = `${TEST_HOST}/endpoint.json`; + mock = new MockAdapter(axios); + }); + + afterEach(() => { + stopJobsPolling(); + clearJobsEtagPoll(); + }); + + afterEach(() => mock.restore()); + + it('dispatches requestJobs and receiveJobsSuccess actions on a successful request', done => { + const updatedProjects = [{ name: 'imported/project' }, { name: 'provider/repo' }]; + mock.onGet(`${TEST_HOST}/endpoint.json`).reply(200, updatedProjects); + + testAction( + fetchJobs, + null, + localState, + [], + [ + { + type: 'receiveJobsSuccess', + payload: convertObjectPropsToCamelCase(updatedProjects, { deep: true }), + }, + ], + done, + ); + }); + }); +}); diff --git a/spec/javascripts/import_projects/store/getters_spec.js b/spec/javascripts/import_projects/store/getters_spec.js new file mode 100644 index 00000000000..e5e4a95f473 --- /dev/null +++ b/spec/javascripts/import_projects/store/getters_spec.js @@ -0,0 +1,83 @@ +import { + namespaceSelectOptions, + isImportingAnyRepo, + hasProviderRepos, + hasImportedProjects, +} from '~/import_projects/store/getters'; +import state from '~/import_projects/store/state'; + +describe('import_projects store getters', () => { + let localState; + + beforeEach(() => { + localState = state(); + }); + + describe('namespaceSelectOptions', () => { + const namespaces = [{ fullPath: 'namespace-0' }, { fullPath: 'namespace-1' }]; + const defaultTargetNamespace = 'current-user'; + + it('returns an options array with a "Users" and "Groups" optgroups', () => { + localState.namespaces = namespaces; + localState.defaultTargetNamespace = defaultTargetNamespace; + + const optionsArray = namespaceSelectOptions(localState); + const groupsGroup = optionsArray[0]; + const usersGroup = optionsArray[1]; + + expect(groupsGroup.text).toBe('Groups'); + expect(usersGroup.text).toBe('Users'); + + groupsGroup.children.forEach((child, index) => { + expect(child.id).toBe(namespaces[index].fullPath); + expect(child.text).toBe(namespaces[index].fullPath); + }); + + expect(usersGroup.children.length).toBe(1); + expect(usersGroup.children[0].id).toBe(defaultTargetNamespace); + expect(usersGroup.children[0].text).toBe(defaultTargetNamespace); + }); + }); + + describe('isImportingAnyRepo', () => { + it('returns true if there are any reposBeingImported', () => { + localState.reposBeingImported = new Array(1); + + expect(isImportingAnyRepo(localState)).toBe(true); + }); + + it('returns false if there are no reposBeingImported', () => { + localState.reposBeingImported = []; + + expect(isImportingAnyRepo(localState)).toBe(false); + }); + }); + + describe('hasProviderRepos', () => { + it('returns true if there are any providerRepos', () => { + localState.providerRepos = new Array(1); + + expect(hasProviderRepos(localState)).toBe(true); + }); + + it('returns false if there are no providerRepos', () => { + localState.providerRepos = []; + + expect(hasProviderRepos(localState)).toBe(false); + }); + }); + + describe('hasImportedProjects', () => { + it('returns true if there are any importedProjects', () => { + localState.importedProjects = new Array(1); + + expect(hasImportedProjects(localState)).toBe(true); + }); + + it('returns false if there are no importedProjects', () => { + localState.importedProjects = []; + + expect(hasImportedProjects(localState)).toBe(false); + }); + }); +}); diff --git a/spec/javascripts/import_projects/store/mutations_spec.js b/spec/javascripts/import_projects/store/mutations_spec.js new file mode 100644 index 00000000000..8db8e9819ba --- /dev/null +++ b/spec/javascripts/import_projects/store/mutations_spec.js @@ -0,0 +1,34 @@ +import * as types from '~/import_projects/store/mutation_types'; +import mutations from '~/import_projects/store/mutations'; + +describe('import_projects store mutations', () => { + describe(types.RECEIVE_IMPORT_SUCCESS, () => { + it('removes repoId from reposBeingImported and providerRepos, adds to importedProjects', () => { + const repoId = 1; + const state = { + reposBeingImported: [repoId], + providerRepos: [{ id: repoId }], + importedProjects: [], + }; + const importedProject = { id: repoId }; + + mutations[types.RECEIVE_IMPORT_SUCCESS](state, { importedProject, repoId }); + + expect(state.reposBeingImported.includes(repoId)).toBe(false); + expect(state.providerRepos.some(repo => repo.id === repoId)).toBe(false); + expect(state.importedProjects.some(repo => repo.id === repoId)).toBe(true); + }); + }); + + describe(types.RECEIVE_JOBS_SUCCESS, () => { + it('updates importStatus of existing importedProjects', () => { + const repoId = 1; + const state = { importedProjects: [{ id: repoId, importStatus: 'started' }] }; + const updatedProjects = [{ id: repoId, importStatus: 'finished' }]; + + mutations[types.RECEIVE_JOBS_SUCCESS](state, updatedProjects); + + expect(state.importedProjects[0].importStatus).toBe(updatedProjects[0].importStatus); + }); + }); +}); |