summaryrefslogtreecommitdiff
path: root/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js')
-rw-r--r--spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js249
1 files changed, 249 insertions, 0 deletions
diff --git a/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js b/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js
new file mode 100644
index 00000000000..b4ac11b4404
--- /dev/null
+++ b/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js
@@ -0,0 +1,249 @@
+import { nextTick } from 'vue';
+import Vuex from 'vuex';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon, GlButton, GlIntersectionObserver } from '@gitlab/ui';
+import state from '~/import_entities/import_projects/store/state';
+import * as getters from '~/import_entities/import_projects/store/getters';
+import { STATUSES } from '~/import_entities/constants';
+import ImportProjectsTable from '~/import_entities/import_projects/components/import_projects_table.vue';
+import ProviderRepoTableRow from '~/import_entities/import_projects/components/provider_repo_table_row.vue';
+
+describe('ImportProjectsTable', () => {
+ let wrapper;
+
+ const findFilterField = () =>
+ wrapper.find('input[data-qa-selector="githubish_import_filter_field"]');
+
+ const providerTitle = 'THE PROVIDER';
+ const providerRepo = {
+ importSource: {
+ id: 10,
+ sanitizedName: 'sanitizedName',
+ fullName: 'fullName',
+ },
+ importedProject: null,
+ };
+
+ const findImportAllButton = () =>
+ wrapper
+ .findAll(GlButton)
+ .filter(w => w.props().variant === 'success')
+ .at(0);
+ const findImportAllModal = () => wrapper.find({ ref: 'importAllModal' });
+
+ const importAllFn = jest.fn();
+ const importAllModalShowFn = jest.fn();
+ const fetchReposFn = jest.fn();
+
+ function createComponent({
+ state: initialState,
+ getters: customGetters,
+ slots,
+ filterable,
+ paginatable,
+ } = {}) {
+ const localVue = createLocalVue();
+ localVue.use(Vuex);
+
+ const store = new Vuex.Store({
+ state: { ...state(), ...initialState },
+ getters: {
+ ...getters,
+ ...customGetters,
+ },
+ actions: {
+ fetchRepos: fetchReposFn,
+ fetchJobs: jest.fn(),
+ fetchNamespaces: jest.fn(),
+ importAll: importAllFn,
+ stopJobsPolling: jest.fn(),
+ clearJobsEtagPoll: jest.fn(),
+ setFilter: jest.fn(),
+ },
+ });
+
+ wrapper = shallowMount(ImportProjectsTable, {
+ localVue,
+ store,
+ propsData: {
+ providerTitle,
+ filterable,
+ paginatable,
+ },
+ slots,
+ stubs: {
+ GlModal: { template: '<div>Modal!</div>', methods: { show: importAllModalShowFn } },
+ },
+ });
+ }
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ wrapper = null;
+ }
+ });
+
+ it('renders a loading icon while repos are loading', () => {
+ createComponent({ state: { isLoadingRepos: true } });
+
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ });
+
+ it('renders a loading icon while namespaces are loading', () => {
+ createComponent({ state: { isLoadingNamespaces: true } });
+
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ });
+
+ 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 },
+ });
+
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.find('table').exists()).toBe(true);
+ expect(
+ wrapper
+ .findAll('th')
+ .filter(w => w.text() === `From ${providerTitle}`)
+ .exists(),
+ ).toBe(true);
+
+ expect(wrapper.findAll(ProviderRepoTableRow)).toHaveLength(repositories.length);
+ });
+
+ it.each`
+ hasIncompatibleRepos | count | buttonText
+ ${false} | ${1} | ${'Import 1 repository'}
+ ${true} | ${1} | ${'Import 1 compatible repository'}
+ ${false} | ${5} | ${'Import 5 repositories'}
+ ${true} | ${5} | ${'Import 5 compatible repositories'}
+ `(
+ 'import all button has "$buttonText" text when hasIncompatibleRepos is $hasIncompatibleRepos and repos count is $count',
+ ({ hasIncompatibleRepos, buttonText, count }) => {
+ createComponent({
+ state: {
+ providerRepos: [providerRepo],
+ },
+ getters: {
+ hasIncompatibleRepos: () => hasIncompatibleRepos,
+ importAllCount: () => count,
+ },
+ });
+
+ expect(findImportAllButton().text()).toBe(buttonText);
+ },
+ );
+
+ it('renders an empty state if there are no repositories available', () => {
+ createComponent({ state: { repositories: [] } });
+
+ expect(wrapper.find(ProviderRepoTableRow).exists()).toBe(false);
+ expect(wrapper.text()).toContain(`No ${providerTitle} repositories found`);
+ });
+
+ it('opens confirmation modal when import all button is clicked', async () => {
+ createComponent({ state: { repositories: [providerRepo] } });
+
+ findImportAllButton().vm.$emit('click');
+ await nextTick();
+
+ expect(importAllModalShowFn).toHaveBeenCalled();
+ });
+
+ it('triggers importAll action when modal is confirmed', async () => {
+ createComponent({ state: { providerRepos: [providerRepo] } });
+
+ findImportAllModal().vm.$emit('ok');
+ await nextTick();
+
+ expect(importAllFn).toHaveBeenCalled();
+ });
+
+ it('shows loading spinner when import is in progress', () => {
+ createComponent({ getters: { isImportingAnyRepo: () => true } });
+
+ expect(findImportAllButton().props().loading).toBe(true);
+ });
+
+ it('renders filtering input field by default', () => {
+ createComponent();
+
+ expect(findFilterField().exists()).toBe(true);
+ });
+
+ it('does not render filtering input field when filterable is false', () => {
+ createComponent({ filterable: false });
+
+ expect(findFilterField().exists()).toBe(false);
+ });
+
+ describe('when paginatable is set to true', () => {
+ const pageInfo = { page: 1 };
+
+ beforeEach(() => {
+ createComponent({
+ state: {
+ namespaces: [{ fullPath: 'path' }],
+ pageInfo,
+ repositories: [
+ { importSource: { id: 1 }, importedProject: null, importStatus: STATUSES.NONE },
+ ],
+ },
+ paginatable: true,
+ });
+ });
+
+ it('does not call fetchRepos on mount', () => {
+ expect(fetchReposFn).not.toHaveBeenCalled();
+ });
+
+ it('renders intersection observer component', () => {
+ expect(wrapper.find(GlIntersectionObserver).exists()).toBe(true);
+ });
+
+ it('calls fetchRepos when intersection observer appears', async () => {
+ wrapper.find(GlIntersectionObserver).vm.$emit('appear');
+
+ await nextTick();
+
+ expect(fetchReposFn).toHaveBeenCalled();
+ });
+ });
+
+ it('calls fetchRepos on mount', () => {
+ createComponent();
+
+ expect(fetchReposFn).toHaveBeenCalled();
+ });
+
+ 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,
+ },
+ });
+
+ expect(wrapper.text().includes(INCOMPATIBLE_TEXT)).toBe(shouldRenderSlot);
+ },
+ );
+});