diff --git a/.dockerignore b/.dockerignore index d5568619169..2d1af2c25fc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,6 +3,7 @@ # Ignore all folders except qa/, config/initializers and the root of lib/ since # the files we need to build the QA image are in these folders. # Following are the files we need: +# - ./config/light_settings.rb # - ./config/initializers/0_inject_enterprise_edition_module.rb # - ./ee/app/models/license.rb # - ./lib/gitlab.rb diff --git a/app/models/deployment.rb b/app/models/deployment.rb index db7f9e06362..30694313f7a 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -180,3 +180,5 @@ class Deployment < ApplicationRecord self.created_at if success? && !read_attribute(:finished_at) end end + +Deployment.prepend_if_ee('EE::Deployment') diff --git a/app/models/release.rb b/app/models/release.rb index 9117a475ee9..8759e38060c 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -67,3 +67,5 @@ class Release < ApplicationRecord end end end + +Release.prepend_if_ee('EE::Release') diff --git a/qa/Dockerfile b/qa/Dockerfile index 84dbfae1008..97c2cd482f5 100644 --- a/qa/Dockerfile +++ b/qa/Dockerfile @@ -49,13 +49,15 @@ RUN export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" && \ WORKDIR /home/gitlab/qa COPY ./qa/Gemfile* /home/gitlab/qa/ +COPY ./config/light_settings.rb /home/gitlab/config/light_settings.rb COPY ./config/initializers/0_inject_enterprise_edition_module.rb /home/gitlab/config/initializers/ -# Copy VERSION to ensure the COPY succeeds to copy at least one file since ee/app/models/license.rb isn't present in CE +# Copy VERSION to ensure the COPY succeeds to copy at least one file since ee/app/models/license.rb isn't present in FOSS +# The [b] part makes ./ee/app/models/license.r[b] a pattern that is allowed to return no files (which is the case in FOSS) COPY VERSION ./ee/app/models/license.r[b] /home/gitlab/ee/app/models/ +COPY ./config/light_settings.rb /home/gitlab/config/ COPY ./lib/gitlab.rb /home/gitlab/lib/ -COPY ./INSTALLATION_TYPE /home/gitlab/ -COPY ./VERSION /home/gitlab/ -RUN cd /home/gitlab/qa/ && bundle install +COPY ./INSTALLATION_TYPE ./VERSION /home/gitlab/ +RUN cd /home/gitlab/qa/ && bundle install --jobs=$(nproc) --retry=3 --quiet COPY ./qa /home/gitlab/qa ENTRYPOINT ["bin/test"] diff --git a/spec/frontend/ide/components/merge_requests/list_spec.js b/spec/frontend/ide/components/merge_requests/list_spec.js new file mode 100644 index 00000000000..86a311acad4 --- /dev/null +++ b/spec/frontend/ide/components/merge_requests/list_spec.js @@ -0,0 +1,214 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; +import List from '~/ide/components/merge_requests/list.vue'; +import Item from '~/ide/components/merge_requests/item.vue'; +import TokenedInput from '~/ide/components/shared/tokened_input.vue'; +import { GlLoadingIcon } from '@gitlab/ui'; +import { mergeRequests as mergeRequestsMock } from '../../mock_data'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('IDE merge requests list', () => { + let wrapper; + let fetchMergeRequestsMock; + + const findSearchTypeButtons = () => wrapper.findAll('button'); + const findTokenedInput = () => wrapper.find(TokenedInput); + + const createComponent = (state = {}) => { + const { mergeRequests = {}, ...restOfState } = state; + const fakeStore = new Vuex.Store({ + state: { + currentMergeRequestId: '1', + currentProjectId: 'project/master', + ...restOfState, + }, + modules: { + mergeRequests: { + namespaced: true, + state: { + isLoading: false, + mergeRequests: [], + ...mergeRequests, + }, + actions: { + fetchMergeRequests: fetchMergeRequestsMock, + }, + }, + }, + }); + + wrapper = shallowMount(List, { + store: fakeStore, + localVue, + sync: false, + }); + }; + + beforeEach(() => { + fetchMergeRequestsMock = jest.fn(); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('calls fetch on mounted', () => { + createComponent(); + expect(fetchMergeRequestsMock).toHaveBeenCalledWith( + expect.any(Object), + { + search: '', + type: '', + }, + undefined, + ); + }); + + it('renders loading icon when merge request is loading', () => { + createComponent({ mergeRequests: { isLoading: true } }); + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + }); + + it('renders no search results text when search is not empty', () => { + createComponent(); + findTokenedInput().vm.$emit('input', 'something'); + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.text()).toContain('No merge requests found'); + }); + }); + + it('clicking on search type, sets currentSearchType and loads merge requests', () => { + createComponent(); + findTokenedInput().vm.$emit('focus'); + + return wrapper.vm + .$nextTick() + .then(() => { + findSearchTypeButtons() + .at(0) + .trigger('click'); + return wrapper.vm.$nextTick(); + }) + .then(() => { + const searchType = wrapper.vm.$options.searchTypes[0]; + + expect(findTokenedInput().props('tokens')).toEqual([searchType]); + expect(fetchMergeRequestsMock).toHaveBeenCalledWith( + expect.any(Object), + { + type: searchType.type, + search: '', + }, + undefined, + ); + }); + }); + + describe('with merge requests', () => { + let defaultStateWithMergeRequests; + + beforeAll(() => { + defaultStateWithMergeRequests = { + mergeRequests: { + isLoading: false, + mergeRequests: [ + { ...mergeRequestsMock[0], projectPathWithNamespace: 'gitlab-org/gitlab-foss' }, + ], + }, + }; + }); + + it('renders list', () => { + createComponent(defaultStateWithMergeRequests); + + expect(wrapper.findAll(Item).length).toBe(1); + expect(wrapper.find(Item).props('item')).toBe( + defaultStateWithMergeRequests.mergeRequests.mergeRequests[0], + ); + }); + + describe('when searching merge requests', () => { + it('calls `loadMergeRequests` on input in search field', () => { + createComponent(defaultStateWithMergeRequests); + const input = findTokenedInput(); + input.vm.$emit('input', 'something'); + fetchMergeRequestsMock.mockClear(); + + jest.runAllTimers(); + return wrapper.vm.$nextTick().then(() => { + expect(fetchMergeRequestsMock).toHaveBeenCalledWith( + expect.any(Object), + { + search: 'something', + type: '', + }, + undefined, + ); + }); + }); + }); + }); + + describe('on search focus', () => { + let input; + + beforeEach(() => { + createComponent(); + input = findTokenedInput(); + }); + + describe('without search value', () => { + beforeEach(() => { + input.vm.$emit('focus'); + return wrapper.vm.$nextTick(); + }); + + it('shows search types', () => { + const buttons = findSearchTypeButtons(); + expect(buttons.wrappers.map(x => x.text().trim())).toEqual( + wrapper.vm.$options.searchTypes.map(x => x.label), + ); + }); + + it('hides search types when search changes', () => { + input.vm.$emit('input', 'something'); + + return wrapper.vm.$nextTick().then(() => { + expect(findSearchTypeButtons().exists()).toBe(false); + }); + }); + + describe('with search type', () => { + beforeEach(() => { + findSearchTypeButtons() + .at(0) + .trigger('click'); + + return wrapper.vm + .$nextTick() + .then(() => input.vm.$emit('focus')) + .then(() => wrapper.vm.$nextTick()); + }); + + it('does not show search types', () => { + expect(findSearchTypeButtons().exists()).toBe(false); + }); + }); + }); + + describe('with search value', () => { + beforeEach(() => { + input.vm.$emit('input', 'something'); + input.vm.$emit('focus'); + return wrapper.vm.$nextTick(); + }); + + it('does not show search types', () => { + expect(findSearchTypeButtons().exists()).toBe(false); + }); + }); + }); +}); diff --git a/spec/javascripts/ide/components/merge_requests/list_spec.js b/spec/javascripts/ide/components/merge_requests/list_spec.js deleted file mode 100644 index 55e4f46d9ca..00000000000 --- a/spec/javascripts/ide/components/merge_requests/list_spec.js +++ /dev/null @@ -1,159 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import List from '~/ide/components/merge_requests/list.vue'; -import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; -import { mergeRequests } from '../../mock_data'; -import { resetStore } from '../../helpers'; - -describe('IDE merge requests list', () => { - const Component = Vue.extend(List); - let vm; - - beforeEach(() => { - vm = createComponentWithStore(Component, store, {}); - - spyOn(vm, 'fetchMergeRequests'); - - vm.$mount(); - }); - - afterEach(() => { - vm.$destroy(); - - resetStore(vm.$store); - }); - - it('calls fetch on mounted', () => { - expect(vm.fetchMergeRequests).toHaveBeenCalledWith({ - search: '', - type: '', - }); - }); - - it('renders loading icon', done => { - vm.$store.state.mergeRequests.isLoading = true; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.loading-container')).not.toBe(null); - - done(); - }); - }); - - it('renders no search results text when search is not empty', done => { - vm.search = 'testing'; - - vm.$nextTick(() => { - expect(vm.$el.textContent).toContain('No merge requests found'); - - done(); - }); - }); - - it('clicking on search type, sets currentSearchType and loads merge requests', done => { - vm.onSearchFocus(); - - vm.$nextTick() - .then(() => { - vm.$el.querySelector('li button').click(); - - return vm.$nextTick(); - }) - .then(() => { - expect(vm.currentSearchType).toEqual(vm.$options.searchTypes[0]); - expect(vm.fetchMergeRequests).toHaveBeenCalledWith({ - type: vm.currentSearchType.type, - search: '', - }); - }) - .then(done) - .catch(done.fail); - }); - - describe('with merge requests', () => { - beforeEach(done => { - vm.$store.state.mergeRequests.mergeRequests.push({ - ...mergeRequests[0], - projectPathWithNamespace: 'gitlab-org/gitlab-ce', - }); - - vm.$nextTick(done); - }); - - it('renders list', () => { - expect(vm.$el.querySelectorAll('li').length).toBe(1); - expect(vm.$el.querySelector('li').textContent).toContain(mergeRequests[0].title); - }); - }); - - describe('searchMergeRequests', () => { - beforeEach(() => { - spyOn(vm, 'loadMergeRequests'); - - jasmine.clock().install(); - }); - - afterEach(() => { - jasmine.clock().uninstall(); - }); - - it('calls loadMergeRequests on input in search field', () => { - const event = new Event('input'); - - vm.$el.querySelector('input').dispatchEvent(event); - - jasmine.clock().tick(300); - - expect(vm.loadMergeRequests).toHaveBeenCalled(); - }); - }); - - describe('onSearchFocus', () => { - it('shows search types', done => { - vm.$el.querySelector('input').dispatchEvent(new Event('focus')); - - expect(vm.hasSearchFocus).toBe(true); - expect(vm.showSearchTypes).toBe(true); - - vm.$nextTick() - .then(() => { - const expectedSearchTypes = vm.$options.searchTypes.map(x => x.label); - const renderedSearchTypes = Array.from(vm.$el.querySelectorAll('li')).map(x => - x.textContent.trim(), - ); - - expect(renderedSearchTypes).toEqual(expectedSearchTypes); - }) - .then(done) - .catch(done.fail); - }); - - it('does not show search types, if already has search value', () => { - vm.search = 'lorem ipsum'; - vm.$el.querySelector('input').dispatchEvent(new Event('focus')); - - expect(vm.hasSearchFocus).toBe(true); - expect(vm.showSearchTypes).toBe(false); - }); - - it('does not show search types, if already has a search type', () => { - vm.currentSearchType = {}; - vm.$el.querySelector('input').dispatchEvent(new Event('focus')); - - expect(vm.hasSearchFocus).toBe(true); - expect(vm.showSearchTypes).toBe(false); - }); - - it('resets hasSearchFocus when search changes', done => { - vm.hasSearchFocus = true; - vm.search = 'something else'; - - vm.$nextTick() - .then(() => { - expect(vm.hasSearchFocus).toBe(false); - }) - .then(done) - .catch(done.fail); - }); - }); -});