Commit 283a5b0d authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch 'xanf-make-import-status-compatible-with-new-format' into 'master'

Refactor storage of import_status

See merge request gitlab-org/gitlab!40160
parents fc5cff46 059a5b43
......@@ -4,20 +4,15 @@ import { mapActions, mapState, mapGetters } from 'vuex';
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import PaginationLinks from '~/vue_shared/components/pagination_links.vue';
import ImportedProjectTableRow from './imported_project_table_row.vue';
import ProviderRepoTableRow from './provider_repo_table_row.vue';
import IncompatibleRepoTableRow from './incompatible_repo_table_row.vue';
import PageQueryParamSync from './page_query_param_sync.vue';
import { isProjectImportable } from '../utils';
const reposFetchThrottleDelay = 1000;
export default {
name: 'ImportProjectsTable',
components: {
ImportedProjectTableRow,
ProviderRepoTableRow,
IncompatibleRepoTableRow,
PageQueryParamSync,
GlLoadingIcon,
GlButton,
......@@ -109,8 +104,6 @@ export default {
throttledFetchRepos: throttle(function fetch() {
this.fetchRepos();
}, reposFetchThrottleDelay),
isProjectImportable,
},
};
</script>
......@@ -165,18 +158,11 @@ export default {
</thead>
<tbody>
<template v-for="repo in repositories">
<incompatible-repo-table-row
v-if="repo.importSource.incompatible"
:key="repo.importSource.id"
:repo="repo"
/>
<provider-repo-table-row
v-else-if="isProjectImportable(repo)"
:key="repo.importSource.id"
:key="repo.importSource.providerLink"
:repo="repo"
:available-namespaces="availableNamespaces"
/>
<imported-project-table-row v-else :key="repo.importSource.id" :project="repo" />
</template>
</tbody>
</table>
......
<script>
import { GlIcon } from '@gitlab/ui';
import ImportStatus from './import_status.vue';
import { STATUSES } from '../constants';
export default {
name: 'ImportedProjectTableRow',
components: {
ImportStatus,
GlIcon,
},
props: {
project: {
type: Object,
required: true,
},
},
computed: {
displayFullPath() {
return this.project.importedProject.fullPath.replace(/^\//, '');
},
isFinished() {
return this.project.importStatus === STATUSES.FINISHED;
},
},
};
</script>
<template>
<tr class="import-row">
<td>
<a
:href="project.importSource.providerLink"
rel="noreferrer noopener"
target="_blank"
data-testid="providerLink"
>{{ project.importSource.fullName }}
<gl-icon v-if="project.importSource.providerLink" name="external-link" />
</a>
</td>
<td data-testid="fullPath">{{ displayFullPath }}</td>
<td>
<import-status :status="project.importStatus" />
</td>
<td>
<a
v-if="isFinished"
class="btn btn-default"
data-testid="goToProject"
:href="project.importedProject.fullPath"
rel="noreferrer noopener"
target="_blank"
>{{ __('Go to project') }}
</a>
</td>
</tr>
</template>
<script>
import { GlIcon, GlBadge } from '@gitlab/ui';
export default {
components: {
GlBadge,
GlIcon,
},
props: {
repo: {
type: Object,
required: true,
},
},
};
</script>
<template>
<tr class="import-row">
<td>
<a :href="repo.importSource.providerLink" rel="noreferrer noopener" target="_blank"
>{{ repo.importSource.fullName }}
<gl-icon v-if="repo.importSource.providerLink" name="external-link" />
</a>
</td>
<td></td>
<td></td>
<td>
<gl-badge variant="danger">{{ __('Incompatible project') }}</gl-badge>
</td>
</tr>
</template>
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import { GlIcon } from '@gitlab/ui';
import { GlIcon, GlBadge } from '@gitlab/ui';
import Select2Select from '~/vue_shared/components/select2_select.vue';
import { __ } from '~/locale';
import ImportStatus from './import_status.vue';
import { STATUSES } from '../constants';
import { isProjectImportable, isIncompatible, getImportStatus } from '../utils';
export default {
name: 'ProviderRepoTableRow',
......@@ -11,6 +13,7 @@ export default {
Select2Select,
ImportStatus,
GlIcon,
GlBadge,
},
props: {
repo: {
......@@ -27,6 +30,26 @@ export default {
...mapState(['ciCdOnly']),
...mapGetters(['getImportTarget']),
displayFullPath() {
return this.repo.importedProject.fullPath.replace(/^\//, '');
},
isFinished() {
return this.repo.importedProject?.importStatus === STATUSES.FINISHED;
},
isImportNotStarted() {
return isProjectImportable(this.repo);
},
isIncompatible() {
return isIncompatible(this.repo);
},
importStatus() {
return getImportStatus(this.repo);
},
importTarget() {
return this.getImportTarget(this.repo.importSource.id);
},
......@@ -85,9 +108,9 @@ export default {
<gl-icon v-if="repo.importSource.providerLink" name="external-link" />
</a>
</td>
<td class="d-flex flex-wrap flex-lg-nowrap">
<template v-if="repo.target">{{ repo.target }}</template>
<template v-else>
<td class="d-flex flex-wrap flex-lg-nowrap" data-testid="fullPath">
<template v-if="repo.importSource.target">{{ repo.importSource.target }}</template>
<template v-else-if="isImportNotStarted">
<select2-select v-model="targetNamespaceSelect" :options="select2Options" />
<span class="px-2 import-slash-divider d-flex justify-content-center align-items-center"
>/</span
......@@ -98,18 +121,31 @@ export default {
class="form-control import-project-name-input qa-project-path-field"
/>
</template>
<template v-else-if="repo.importedProject">{{ displayFullPath }}</template>
</td>
<td>
<import-status :status="repo.importStatus" />
<import-status :status="importStatus" />
</td>
<td>
<td data-testid="actions">
<a
v-if="isFinished"
class="btn btn-default"
:href="repo.importedProject.fullPath"
rel="noreferrer noopener"
target="_blank"
>{{ __('Go to project') }}
</a>
<button
v-if="isImportNotStarted"
type="button"
class="qa-import-button btn btn-default"
@click="fetchImport(repo.importSource.id)"
>
{{ importButtonText }}
</button>
<gl-badge v-else-if="isIncompatible" variant="danger">{{
__('Incompatible project')
}}</gl-badge>
</td>
</tr>
</template>
import { STATUSES } from '../constants';
import { isProjectImportable, isIncompatible } from '../utils';
export const isLoading = state => state.isLoadingRepos || state.isLoadingNamespaces;
export const isImportingAnyRepo = state =>
state.repositories.some(repo =>
[STATUSES.SCHEDULING, STATUSES.SCHEDULED, STATUSES.STARTED].includes(repo.importStatus),
[STATUSES.SCHEDULING, STATUSES.SCHEDULED, STATUSES.STARTED].includes(
repo.importedProject?.importStatus,
),
);
export const hasIncompatibleRepos = state =>
state.repositories.some(repo => repo.importSource.incompatible);
export const hasIncompatibleRepos = state => state.repositories.some(isIncompatible);
export const hasImportableRepos = state =>
state.repositories.some(repo => repo.importStatus === STATUSES.NONE);
export const hasImportableRepos = state => state.repositories.some(isProjectImportable);
export const getImportTarget = state => repoId => {
if (state.customImportTargets[repoId]) {
......
......@@ -11,37 +11,36 @@ export default {
state.isLoadingRepos = true;
},
[types.RECEIVE_REPOS_SUCCESS](
state,
{ importedProjects, providerRepos, incompatibleRepos = [] },
) {
// Normalizing structure to support legacy backend format
// See https://gitlab.com/gitlab-org/gitlab/-/issues/27370#note_379034091 for details
[types.RECEIVE_REPOS_SUCCESS](state, repositories) {
state.isLoadingRepos = false;
state.repositories = [
...importedProjects.map(({ importSource, providerLink, importStatus, ...project }) => ({
importSource: {
id: `finished-${project.id}`,
fullName: importSource,
sanitizedName: project.name,
providerLink,
},
importStatus,
importedProject: project,
})),
...providerRepos.map(project => ({
importSource: project,
importStatus: STATUSES.NONE,
importedProject: null,
})),
...incompatibleRepos.map(project => ({
importSource: { ...project, incompatible: true },
importStatus: STATUSES.NONE,
importedProject: null,
})),
];
if (!Array.isArray(repositories)) {
// Legacy code path, will be removed when all importers will be switched to new pagination format
// https://gitlab.com/gitlab-org/gitlab/-/issues/27370#note_379034091
state.repositories = [
...repositories.importedProjects.map(importedProject => ({
importSource: {
id: importedProject.id,
fullName: importedProject.importSource,
sanitizedName: importedProject.name,
providerLink: importedProject.providerLink,
},
importedProject,
})),
...repositories.providerRepos.map(project => ({
importSource: project,
importedProject: null,
})),
...(repositories.incompatibleRepos ?? []).map(project => ({
importSource: { ...project, incompatible: true },
importedProject: null,
})),
];
return;
}
state.repositories = repositories;
},
[types.RECEIVE_REPOS_ERROR](state) {
......@@ -50,31 +49,27 @@ export default {
[types.REQUEST_IMPORT](state, { repoId, importTarget }) {
const existingRepo = state.repositories.find(r => r.importSource.id === repoId);
existingRepo.importStatus = STATUSES.SCHEDULING;
existingRepo.importedProject = {
importStatus: STATUSES.SCHEDULING,
fullPath: `/${importTarget.targetNamespace}/${importTarget.newName}`,
};
},
[types.RECEIVE_IMPORT_SUCCESS](state, { importedProject, repoId }) {
const { importStatus, ...project } = importedProject;
const existingRepo = state.repositories.find(r => r.importSource.id === repoId);
existingRepo.importStatus = importStatus;
existingRepo.importedProject = project;
existingRepo.importedProject = importedProject;
},
[types.RECEIVE_IMPORT_ERROR](state, repoId) {
const existingRepo = state.repositories.find(r => r.importSource.id === repoId);
existingRepo.importStatus = STATUSES.NONE;
existingRepo.importedProject = null;
},
[types.RECEIVE_JOBS_SUCCESS](state, updatedProjects) {
updatedProjects.forEach(updatedProject => {
const repo = state.repositories.find(p => p.importedProject?.id === updatedProject.id);
if (repo) {
repo.importStatus = updatedProject.importStatus;
if (repo?.importedProject) {
repo.importedProject.importStatus = updatedProject.importStatus;
}
});
},
......
import { STATUSES } from './constants';
// Will be expanded in future
export function isIncompatible(project) {
return project.importSource.incompatible;
}
export function getImportStatus(project) {
return project.importedProject?.importStatus ?? STATUSES.NONE;
}
export function isProjectImportable(project) {
return project.importStatus === STATUSES.NONE && !project.importSource.incompatible;
return !isIncompatible(project) && getImportStatus(project) === STATUSES.NONE;
}
......@@ -6,9 +6,7 @@ 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', () => {
......@@ -88,20 +86,15 @@ describe('ImportProjectsTable', () => {
expect(wrapper.contains(GlLoadingIcon)).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);
......@@ -113,9 +106,7 @@ describe('ImportProjectsTable', () => {
.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`
......@@ -142,7 +133,6 @@ describe('ImportProjectsTable', () => {
createComponent({ state: { repositories: [] } });
expect(wrapper.contains(ProviderRepoTableRow)).toBe(false);
expect(wrapper.contains(ImportedProjectTableRow)).toBe(false);
expect(wrapper.text()).toContain(`No ${providerTitle} repositories found`);
});
......
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,
);
});
});
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.contains(Select2Select)).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.contains(Select2Select)).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');
});
});
});
......@@ -10,13 +10,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 +55,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];
......
......@@ -40,93 +40,100 @@ 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 = {};
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 = {};
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 = {};
mutations[types.RECEIVE_REPOS_SUCCESS](state, response);
expect(IMPORTED_PROJECT).toStrictEqual(
expect.objectContaining(state.repositories[0].importedProject),
);
describe('for importable projects', () => {
beforeEach(() => {
state = {};
const response = {
importedProjects: [],
providerRepos: [SOURCE_PROJECT],
};
mutations[types.RECEIVE_REPOS_SUCCESS](state, response);
});
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 = {};
mutations[types.RECEIVE_REPOS_SUCCESS](state, response);
});
it('sets importSource to project', () => {
expect(state.repositories[0].importSource).toBe(SOURCE_PROJECT);
});
});
it('sets incompatible flag', () => {
expect(state.repositories[0].importSource.incompatible).toBe(true);
});
describe('for incompatible projects', () => {
const response = {
importedProjects: [],
providerRepos: [],
incompatibleRepos: [SOURCE_PROJECT],
};
it('sets importSource to project', () => {
expect(state.repositories[0].importSource).toStrictEqual(
expect.objectContaining(SOURCE_PROJECT),
);
});
});
beforeEach(() => {
it('sets repos loading flag to false', () => {
const response = {
importedProjects: [],
providerRepos: [],
};
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 = {};
mutations[types.RECEIVE_REPOS_SUCCESS](state, response);
expect(state.repositories).toBe(response);
});
it('sets repos loading flag to false', () => {
const response = {
importedProjects: [],
providerRepos: [],
};
state = {};
const response = [];
mutations[types.RECEIVE_REPOS_SUCCESS](state, response);
......@@ -154,7 +161,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 +177,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 +197,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 +212,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,
);
});
});
......
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);
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment