diff options
Diffstat (limited to 'spec/frontend/import_projects')
5 files changed, 262 insertions, 298 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 new file mode 100644 index 00000000000..132ccd0e324 --- /dev/null +++ b/spec/frontend/import_projects/components/bitbucket_status_table_spec.js @@ -0,0 +1,59 @@ +import { nextTick } from 'vue'; +import { shallowMount } from '@vue/test-utils'; + +import { GlAlert } from '@gitlab/ui'; +import BitbucketStatusTable from '~/import_projects/components/bitbucket_status_table.vue'; +import ImportProjectsTable from '~/import_projects/components/import_projects_table.vue'; + +const ImportProjectsTableStub = { + name: 'ImportProjectsTable', + template: + '<div><slot name="incompatible-repos-warning"></slot><slot name="actions"></slot></div>', +}; + +describe('BitbucketStatusTable', () => { + let wrapper; + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + wrapper = null; + } + }); + + function createComponent(propsData, importProjectsTableStub = true, slots) { + wrapper = shallowMount(BitbucketStatusTable, { + propsData, + stubs: { + ImportProjectsTable: importProjectsTableStub, + }, + slots, + }); + } + + it('renders import table component', () => { + createComponent({ providerTitle: 'Test' }); + expect(wrapper.contains(ImportProjectsTable)).toBe(true); + }); + + it('passes alert in incompatible-repos-warning slot', () => { + createComponent({ providerTitle: 'Test' }, ImportProjectsTableStub); + expect(wrapper.find(GlAlert).exists()).toBe(true); + }); + + it('passes actions slot to import project table component', () => { + const actionsSlotContent = 'DEMO'; + createComponent({ providerTitle: 'Test' }, ImportProjectsTableStub, { + actions: actionsSlotContent, + }); + expect(wrapper.find(ImportProjectsTable).text()).toBe(actionsSlotContent); + }); + + it('dismisses alert when requested', async () => { + createComponent({ providerTitle: 'Test' }, ImportProjectsTableStub); + wrapper.find(GlAlert).vm.$emit('dismiss'); + await nextTick(); + + expect(wrapper.find(GlAlert).exists()).toBe(false); + }); +}); 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 9491b52c888..419d67e239f 100644 --- a/spec/frontend/import_projects/components/import_projects_table_spec.js +++ b/spec/frontend/import_projects/components/import_projects_table_spec.js @@ -1,11 +1,24 @@ +import { nextTick } from 'vue'; 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'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { GlLoadingIcon, GlButton } from '@gitlab/ui'; +import { state, getters } from '~/import_projects/store'; +import eventHub from '~/import_projects/event_hub'; +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'; + +jest.mock('~/import_projects/event_hub', () => ({ + $emit: jest.fn(), +})); describe('ImportProjectsTable', () => { - let vm; + let wrapper; + + const findFilterField = () => + wrapper.find('input[data-qa-selector="githubish_import_filter_field"]'); + const providerTitle = 'THE PROVIDER'; const providerRepo = { id: 10, sanitizedName: 'sanitizedName', fullName: 'fullName' }; const importedProject = { @@ -16,176 +29,175 @@ describe('ImportProjectsTable', () => { importSource: 'importSource', }; - function initStore() { - const stubbedActions = { - ...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 findImportAllButton = () => + wrapper + .findAll(GlButton) + .filter(w => w.props().variant === 'success') + .at(0); + + function createComponent({ + state: initialState, + getters: customGetters, + slots, + filterable, + } = {}) { const localVue = createLocalVue(); localVue.use(Vuex); - const store = initStore(); + const store = new Vuex.Store({ + state: { ...state(), ...initialState }, + getters: { + ...getters, + ...customGetters, + }, + actions: { + fetchRepos: jest.fn(), + fetchReposFiltered: jest.fn(), + fetchJobs: jest.fn(), + stopJobsPolling: jest.fn(), + clearJobsEtagPoll: jest.fn(), + setFilter: jest.fn(), + }, + }); - const component = mount(importProjectsTable, { + wrapper = shallowMount(ImportProjectsTable, { localVue, store, propsData: { providerTitle, + filterable, }, + slots, }); - - return component.vm; } - beforeEach(() => { - vm = mountComponent(); - }); - afterEach(() => { - vm.$destroy(); + if (wrapper) { + wrapper.destroy(); + wrapper = null; + } }); - it('renders a loading icon while 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' }], + it('renders a loading icon while repos are loading', () => { + createComponent({ + state: { + isLoadingRepos: true, + }, }); - 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(); - }); + expect(wrapper.contains(GlLoadingIcon)).toBe(true); }); - it('renders an empty state if there are no imported projects or provider repos', () => { - vm.$store.dispatch('receiveReposSuccess', { - importedProjects: [], - providerRepos: [], - namespaces: [], + it('renders a table with imported projects and provider repos', () => { + createComponent({ + state: { + importedProjects: [importedProject], + providerRepos: [providerRepo], + incompatibleRepos: [{ ...providerRepo, id: 11 }], + namespaces: [{ path: 'path' }], + }, }); - 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 found`); - }); + expect(wrapper.contains(GlLoadingIcon)).toBe(false); + expect(wrapper.contains('table')).toBe(true); + expect( + wrapper + .findAll('th') + .filter(w => w.text() === `From ${providerTitle}`) + .isEmpty(), + ).toBe(false); + + expect(wrapper.contains(ProviderRepoTableRow)).toBe(true); + expect(wrapper.contains(ImportedProjectTableRow)).toBe(true); + expect(wrapper.contains(IncompatibleRepoTableRow)).toBe(true); }); - 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.each` + hasIncompatibleRepos | buttonText + ${false} | ${'Import all repositories'} + ${true} | ${'Import all compatible repositories'} + `( + 'import all button has "$buttonText" text when hasIncompatibleRepos is $hasIncompatibleRepos', + ({ hasIncompatibleRepos, buttonText }) => { + createComponent({ + state: { + providerRepos: [providerRepo], + }, + getters: { + hasIncompatibleRepos: () => hasIncompatibleRepos, + }, }); - }); - it('imports provider repos if bulk import button is clicked', () => { - mountComponent(); + expect(findImportAllButton().text()).toBe(buttonText); + }, + ); - vm.$store.dispatch('receiveReposSuccess', { - importedProjects: [], - providerRepos: [providerRepo], - namespaces: [{ path: 'path' }], + it('renders an empty state if there are no projects available', () => { + createComponent({ + state: { + importedProjects: [], + providerRepos: [], + incompatibleProjects: [], + }, }); - 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(); - }); + expect(wrapper.contains(ProviderRepoTableRow)).toBe(false); + expect(wrapper.contains(ImportedProjectTableRow)).toBe(false); + expect(wrapper.text()).toContain(`No ${providerTitle} repositories found`); }); - it('polls to update the status of imported projects', () => { - const updatedProjects = [ - { - id: importedProject.id, - importStatus: 'finished', + it('sends importAll event when import button is clicked', async () => { + createComponent({ + state: { + providerRepos: [providerRepo], }, - ]; - - vm.$store.dispatch('receiveReposSuccess', { - importedProjects: [importedProject], - providerRepos: [], - namespaces: [{ path: 'path' }], }); - return vm - .$nextTick() - .then(() => { - const statusObject = STATUS_MAP[importedProject.importStatus]; + findImportAllButton().vm.$emit('click'); + await nextTick(); + expect(eventHub.$emit).toHaveBeenCalledWith('importAll'); + }); - expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull(); - expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch( - statusObject.text, - ); + it('shows loading spinner when import is in progress', () => { + createComponent({ + getters: { + isImportingAnyRepo: () => true, + }, + }); - expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull(); + expect(findImportAllButton().props().loading).toBe(true); + }); - vm.$store.dispatch('receiveJobsSuccess', updatedProjects); - }) - .then(() => vm.$nextTick()) - .then(() => { - const statusObject = STATUS_MAP[updatedProjects[0].importStatus]; + it('renders filtering input field by default', () => { + createComponent(); + expect(findFilterField().exists()).toBe(true); + }); - expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull(); - expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch( - statusObject.text, - ); + it('does not render filtering input field when filterable is false', () => { + createComponent({ filterable: false }); + expect(findFilterField().exists()).toBe(false); + }); - expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull(); + it.each` + hasIncompatibleRepos | shouldRenderSlot | action + ${false} | ${false} | ${'does not render'} + ${true} | ${true} | ${'render'} + `( + '$action incompatible-repos-warning slot if hasIncompatibleRepos is $hasIncompatibleRepos', + ({ hasIncompatibleRepos, shouldRenderSlot }) => { + const INCOMPATIBLE_TEXT = 'INCOMPATIBLE!'; + + createComponent({ + getters: { + hasIncompatibleRepos: () => hasIncompatibleRepos, + }, + + slots: { + 'incompatible-repos-warning': INCOMPATIBLE_TEXT, + }, }); - }); - it('renders filtering input field', () => { - expect( - vm.$el.querySelector('input[data-qa-selector="githubish_import_filter_field"]'), - ).not.toBeNull(); - }); + expect(wrapper.text().includes(INCOMPATIBLE_TEXT)).toBe(shouldRenderSlot); + }, + ); }); 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 8be645c496f..f5e5141eac8 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 @@ -6,7 +6,7 @@ import STATUS_MAP, { STATUSES } from '~/import_projects/constants'; describe('ProviderRepoTableRow', () => { let vm; - const fetchImport = jest.fn((context, data) => actions.requestImport(context, data)); + const fetchImport = jest.fn(); const importPath = '/import-path'; const defaultTargetNamespace = 'user'; const ciCdOnly = true; @@ -17,11 +17,11 @@ describe('ProviderRepoTableRow', () => { providerLink: 'providerLink', }; - function initStore() { + function initStore(initialState) { const stubbedActions = { ...actions, fetchImport }; const store = new Vuex.Store({ - state: state(), + state: { ...state(), ...initialState }, actions: stubbedActions, mutations, getters, @@ -30,12 +30,11 @@ describe('ProviderRepoTableRow', () => { return store; } - function mountComponent() { + function mountComponent(initialState) { const localVue = createLocalVue(); localVue.use(Vuex); - const store = initStore(); - store.dispatch('setInitialData', { importPath, defaultTargetNamespace, ciCdOnly }); + const store = initStore({ importPath, defaultTargetNamespace, ciCdOnly, ...initialState }); const component = mount(providerRepoTableRow, { localVue, diff --git a/spec/frontend/import_projects/store/actions_spec.js b/spec/frontend/import_projects/store/actions_spec.js index 4954513715e..1f2882a2532 100644 --- a/spec/frontend/import_projects/store/actions_spec.js +++ b/spec/frontend/import_projects/store/actions_spec.js @@ -4,7 +4,6 @@ import { TEST_HOST } from 'helpers/test_constants'; 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, @@ -14,14 +13,7 @@ import { RECEIVE_JOBS_SUCCESS, } from '~/import_projects/store/mutation_types'; import { - setInitialData, - requestRepos, - receiveReposSuccess, - receiveReposError, fetchRepos, - requestImport, - receiveImportSuccess, - receiveImportError, fetchImport, receiveJobsSuccess, fetchJobs, @@ -32,7 +24,6 @@ import state from '~/import_projects/store/state'; 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 } }; @@ -40,61 +31,6 @@ describe('import_projects store actions', () => { 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; const payload = { imported_projects: [{}], provider_repos: [{}], namespaces: [{}] }; @@ -106,39 +42,33 @@ describe('import_projects store actions', () => { afterEach(() => mock.restore()); - it('dispatches stopJobsPolling, requestRepos and receiveReposSuccess actions on a successful request', done => { + it('dispatches stopJobsPolling actions and commits REQUEST_REPOS, RECEIVE_REPOS_SUCCESS mutations on a successful request', () => { mock.onGet(`${TEST_HOST}/endpoint.json`).reply(200, payload); - testAction( + return testAction( fetchRepos, null, localState, - [], [ - { type: 'stopJobsPolling' }, - { type: 'requestRepos' }, + { type: REQUEST_REPOS }, { - type: 'receiveReposSuccess', + type: RECEIVE_REPOS_SUCCESS, payload: convertObjectPropsToCamelCase(payload, { deep: true }), }, - { - type: 'fetchJobs', - }, ], - done, + [{ type: 'stopJobsPolling' }, { type: 'fetchJobs' }], ); }); - it('dispatches stopJobsPolling, requestRepos and receiveReposError actions on an unsuccessful request', done => { + it('dispatches stopJobsPolling action and commits REQUEST_REPOS, RECEIVE_REPOS_ERROR mutations on an unsuccessful request', () => { mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500); - testAction( + return testAction( fetchRepos, null, localState, - [], - [{ type: 'stopJobsPolling' }, { type: 'requestRepos' }, { type: 'receiveReposError' }], - done, + [{ type: REQUEST_REPOS }, { type: RECEIVE_REPOS_ERROR }], + [{ type: 'stopJobsPolling' }], ); }); @@ -147,72 +77,26 @@ describe('import_projects store actions', () => { localState.filter = 'filter'; }); - it('fetches repos with filter applied', done => { + it('fetches repos with filter applied', () => { mock.onGet(`${TEST_HOST}/endpoint.json?filter=filter`).reply(200, payload); - testAction( + return testAction( fetchRepos, null, localState, - [], [ - { type: 'stopJobsPolling' }, - { type: 'requestRepos' }, + { type: REQUEST_REPOS }, { - type: 'receiveReposSuccess', + type: RECEIVE_REPOS_SUCCESS, payload: convertObjectPropsToCamelCase(payload, { deep: true }), }, - { - type: 'fetchJobs', - }, ], - done, + [{ type: 'stopJobsPolling' }, { type: 'fetchJobs' }], ); }); }); }); - 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; @@ -223,56 +107,53 @@ describe('import_projects store actions', () => { afterEach(() => mock.restore()); - it('dispatches requestImport and receiveImportSuccess actions on a successful request', done => { + it('commits REQUEST_IMPORT and REQUEST_IMPORT_SUCCESS mutations on a successful request', () => { const importedProject = { name: 'imported/project' }; const importRepoId = importPayload.repo.id; mock.onPost(`${TEST_HOST}/endpoint.json`).reply(200, importedProject); - testAction( + return testAction( fetchImport, importPayload, localState, - [], [ - { type: 'requestImport', payload: importRepoId }, + { type: REQUEST_IMPORT, payload: importRepoId }, { - type: 'receiveImportSuccess', + type: RECEIVE_IMPORT_SUCCESS, payload: { importedProject: convertObjectPropsToCamelCase(importedProject, { deep: true }), repoId: importRepoId, }, }, ], - done, + [], ); }); - it('dispatches requestImport and receiveImportSuccess actions on an unsuccessful request', done => { + it('commits REQUEST_IMPORT and RECEIVE_IMPORT_ERROR on an unsuccessful request', () => { mock.onPost(`${TEST_HOST}/endpoint.json`).reply(500); - testAction( + return testAction( fetchImport, importPayload, localState, - [], [ - { type: 'requestImport', payload: importPayload.repo.id }, - { type: 'receiveImportError', payload: { repoId: importPayload.repo.id } }, + { type: REQUEST_IMPORT, payload: importPayload.repo.id }, + { type: RECEIVE_IMPORT_ERROR, payload: importPayload.repo.id }, ], - done, + [], ); }); }); describe('receiveJobsSuccess', () => { - it(`commits ${RECEIVE_JOBS_SUCCESS} mutation`, done => { - testAction( + it(`commits ${RECEIVE_JOBS_SUCCESS} mutation`, () => { + return testAction( receiveJobsSuccess, repos, localState, [{ type: RECEIVE_JOBS_SUCCESS, payload: repos }], [], - done, ); }); }); @@ -293,21 +174,20 @@ describe('import_projects store actions', () => { afterEach(() => mock.restore()); - it('dispatches requestJobs and receiveJobsSuccess actions on a successful request', done => { + it('commits RECEIVE_JOBS_SUCCESS mutation on a successful request', async () => { mock.onGet(`${TEST_HOST}/endpoint.json`).reply(200, updatedProjects); - testAction( + await testAction( fetchJobs, null, localState, - [], [ { - type: 'receiveJobsSuccess', + type: RECEIVE_JOBS_SUCCESS, payload: convertObjectPropsToCamelCase(updatedProjects, { deep: true }), }, ], - done, + [], ); }); @@ -316,21 +196,20 @@ describe('import_projects store actions', () => { localState.filter = 'filter'; }); - it('fetches realtime changes with filter applied', done => { + it('fetches realtime changes with filter applied', () => { mock.onGet(`${TEST_HOST}/endpoint.json?filter=filter`).reply(200, updatedProjects); - testAction( + return testAction( fetchJobs, null, localState, - [], [ { - type: 'receiveJobsSuccess', + type: RECEIVE_JOBS_SUCCESS, payload: convertObjectPropsToCamelCase(updatedProjects, { deep: true }), }, ], - done, + [], ); }); }); diff --git a/spec/frontend/import_projects/store/getters_spec.js b/spec/frontend/import_projects/store/getters_spec.js index e5e4a95f473..93d1ed89783 100644 --- a/spec/frontend/import_projects/store/getters_spec.js +++ b/spec/frontend/import_projects/store/getters_spec.js @@ -2,6 +2,7 @@ import { namespaceSelectOptions, isImportingAnyRepo, hasProviderRepos, + hasIncompatibleRepos, hasImportedProjects, } from '~/import_projects/store/getters'; import state from '~/import_projects/store/state'; @@ -80,4 +81,18 @@ describe('import_projects store getters', () => { expect(hasImportedProjects(localState)).toBe(false); }); }); + + describe('hasIncompatibleRepos', () => { + it('returns true if there are any incompatibleProjects', () => { + localState.incompatibleRepos = new Array(1); + + expect(hasIncompatibleRepos(localState)).toBe(true); + }); + + it('returns false if there are no incompatibleProjects', () => { + localState.incompatibleRepos = []; + + expect(hasIncompatibleRepos(localState)).toBe(false); + }); + }); }); |