Move import projects karma specs to jest

Improvements to the stability/validity of the specs.
This commit is contained in:
Luke Bennett 2019-04-16 14:54:19 +01:00
parent 8bbb51a63b
commit 642c7252d4
No known key found for this signature in database
GPG key ID: 402ED51FB5D306C2
8 changed files with 256 additions and 242 deletions

View file

@ -74,7 +74,7 @@ export default {
<gl-loading-icon <gl-loading-icon
v-if="isLoadingRepos" v-if="isLoadingRepos"
class="js-loading-button-icon import-projects-loading-icon" class="js-loading-button-icon import-projects-loading-icon"
:size="4" size="md"
/> />
<div v-else-if="hasProviderRepos || hasImportedProjects" class="table-responsive"> <div v-else-if="hasProviderRepos || hasImportedProjects" class="table-responsive">
<table class="table import-table"> <table class="table import-table">

View file

@ -7,6 +7,8 @@ import mutations from './mutations';
Vue.use(Vuex); Vue.use(Vuex);
export { state, actions, getters, mutations };
export default () => export default () =>
new Vuex.Store({ new Vuex.Store({
state: state(), state: state(),

View file

@ -1,6 +1,6 @@
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import 'select2/select2'; import 'select2';
export default { export default {
name: 'Select2Select', name: 'Select2Select',

View file

@ -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();
});
});
});

View file

@ -1,5 +1,6 @@
import Vue from 'vue'; import Vuex from 'vuex';
import createStore from '~/import_projects/store'; 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 importedProjectTableRow from '~/import_projects/components/imported_project_table_row.vue';
import STATUS_MAP from '~/import_projects/constants'; import STATUS_MAP from '~/import_projects/constants';
@ -13,27 +14,33 @@ describe('ImportedProjectTableRow', () => {
importSource: 'importSource', importSource: 'importSource',
}; };
function createComponent() { function mountComponent() {
const ImportedProjectTableRow = Vue.extend(importedProjectTableRow); const localVue = createLocalVue();
localVue.use(Vuex);
const store = createStore(); const component = mount(importedProjectTableRow, {
return new ImportedProjectTableRow({ localVue,
store, store: createStore(),
propsData: { propsData: {
project: { project: {
...project, ...project,
}, },
}, },
}).$mount(); sync: false,
});
return component.vm;
} }
beforeEach(() => {
vm = mountComponent();
});
afterEach(() => { afterEach(() => {
vm.$destroy(); vm.$destroy();
}); });
it('renders an imported project table row', () => { it('renders an imported project table row', () => {
vm = createComponent();
const providerLink = vm.$el.querySelector('.js-provider-link'); const providerLink = vm.$el.querySelector('.js-provider-link');
const statusObject = STATUS_MAP[project.importStatus]; const statusObject = STATUS_MAP[project.importStatus];

View file

@ -1,14 +1,15 @@
import Vue from 'vue'; import Vuex from 'vuex';
import MockAdapter from 'axios-mock-adapter'; import { createLocalVue, mount } from '@vue/test-utils';
import axios from '~/lib/utils/axios_utils'; import { state, actions, getters, mutations } from '~/import_projects/store';
import createStore from '~/import_projects/store';
import providerRepoTableRow from '~/import_projects/components/provider_repo_table_row.vue'; import providerRepoTableRow from '~/import_projects/components/provider_repo_table_row.vue';
import STATUS_MAP, { STATUSES } from '~/import_projects/constants'; import STATUS_MAP, { STATUSES } from '~/import_projects/constants';
import setTimeoutPromise from '../../helpers/set_timeout_promise_helper';
describe('ProviderRepoTableRow', () => { describe('ProviderRepoTableRow', () => {
let store;
let vm; let vm;
const fetchImport = jest.fn((context, data) => actions.requestImport(context, data));
const importPath = '/import-path';
const defaultTargetNamespace = 'user';
const ciCdOnly = true;
const repo = { const repo = {
id: 10, id: 10,
sanitizedName: 'sanitizedName', sanitizedName: 'sanitizedName',
@ -16,21 +17,42 @@ describe('ProviderRepoTableRow', () => {
providerLink: 'providerLink', providerLink: 'providerLink',
}; };
function createComponent() { function initStore() {
const ProviderRepoTableRow = Vue.extend(providerRepoTableRow); 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, store,
propsData: { propsData: {
repo: { repo,
...repo,
},
}, },
}).$mount(); sync: false,
});
return component.vm;
} }
beforeEach(() => { beforeEach(() => {
store = createStore(); vm = mountComponent();
}); });
afterEach(() => { afterEach(() => {
@ -38,8 +60,6 @@ describe('ProviderRepoTableRow', () => {
}); });
it('renders a provider repo table row', () => { it('renders a provider repo table row', () => {
vm = createComponent();
const providerLink = vm.$el.querySelector('.js-provider-link'); const providerLink = vm.$el.querySelector('.js-provider-link');
const statusObject = STATUS_MAP[STATUSES.NONE]; const statusObject = STATUS_MAP[STATUSES.NONE];
@ -55,8 +75,6 @@ describe('ProviderRepoTableRow', () => {
}); });
it('renders a select2 namespace select', () => { it('renders a select2 namespace select', () => {
vm = createComponent();
const dropdownTrigger = vm.$el.querySelector('.js-namespace-select'); const dropdownTrigger = vm.$el.querySelector('.js-namespace-select');
expect(dropdownTrigger).not.toBeNull(); expect(dropdownTrigger).not.toBeNull();
@ -67,30 +85,20 @@ describe('ProviderRepoTableRow', () => {
expect(vm.$el.querySelector('.select2-drop')).not.toBeNull(); expect(vm.$el.querySelector('.select2-drop')).not.toBeNull();
}); });
it('imports repo when clicking import button', done => { it('imports repo when clicking import button', () => {
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(); vm.$el.querySelector('.js-import-button').click();
setTimeoutPromise() return vm.$nextTick().then(() => {
.then(() => { const { calls } = fetchImport.mock;
expect(store.dispatch).toHaveBeenCalledWith('fetchImport', {
repo, // Not using .toBeCalledWith because it expects
newName: repo.sanitizedName, // an unmatchable and undefined 3rd argument.
targetNamespace: defaultTargetNamespace, expect(calls.length).toBe(1);
}); expect(calls[0][1]).toEqual({
}) repo,
.then(() => mock.restore()) newName: repo.sanitizedName,
.then(done) targetNamespace: defaultTargetNamespace,
.catch(done.fail); });
});
}); });
}); });

View file

@ -27,8 +27,8 @@ import {
stopJobsPolling, stopJobsPolling,
} from '~/import_projects/store/actions'; } from '~/import_projects/store/actions';
import state from '~/import_projects/store/state'; import state from '~/import_projects/store/state';
import testAction from 'spec/helpers/vuex_action_helper'; import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'spec/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
describe('import_projects store actions', () => { describe('import_projects store actions', () => {
let localState; let localState;

View file

@ -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());
});
});