diff options
Diffstat (limited to 'spec')
14 files changed, 431 insertions, 416 deletions
diff --git a/spec/fixtures/lib/gitlab/import_export/project.json b/spec/fixtures/lib/gitlab/import_export/complex/project.json index 864933ca1b4..86931d66472 100644 --- a/spec/fixtures/lib/gitlab/import_export/project.json +++ b/spec/fixtures/lib/gitlab/import_export/complex/project.json @@ -6680,6 +6680,25 @@ ] } ] + }, + { + "id": 41, + "project_id": 5, + "ref": "master", + "sha": "2ea1f3dec713d940208fb5ce4a38765ecb5d3f73", + "before_sha": null, + "push_data": null, + "created_at": "2016-03-22T15:20:35.763Z", + "updated_at": "2016-03-22T15:20:35.763Z", + "tag": null, + "yaml_errors": null, + "committed_at": null, + "status": "failed", + "started_at": null, + "finished_at": null, + "duration": null, + "stages": [ + ] } ], "triggers": [ diff --git a/spec/fixtures/lib/gitlab/import_export/project.group.json b/spec/fixtures/lib/gitlab/import_export/group/project.json index 47faf271cca..47faf271cca 100644 --- a/spec/fixtures/lib/gitlab/import_export/project.group.json +++ b/spec/fixtures/lib/gitlab/import_export/group/project.json diff --git a/spec/fixtures/lib/gitlab/import_export/project.light.json b/spec/fixtures/lib/gitlab/import_export/light/project.json index 2971ca0f0f8..2971ca0f0f8 100644 --- a/spec/fixtures/lib/gitlab/import_export/project.light.json +++ b/spec/fixtures/lib/gitlab/import_export/light/project.json diff --git a/spec/fixtures/lib/gitlab/import_export/project.milestone-iid.json b/spec/fixtures/lib/gitlab/import_export/milestone-iid/project.json index b028147b5eb..b028147b5eb 100644 --- a/spec/fixtures/lib/gitlab/import_export/project.milestone-iid.json +++ b/spec/fixtures/lib/gitlab/import_export/milestone-iid/project.json diff --git a/spec/javascripts/vue_shared/components/commit_spec.js b/spec/frontend/vue_shared/components/commit_spec.js index f89627e727b..77d8e00cf00 100644 --- a/spec/javascripts/vue_shared/components/commit_spec.js +++ b/spec/frontend/vue_shared/components/commit_spec.js @@ -1,22 +1,27 @@ -import Vue from 'vue'; -import commitComp from '~/vue_shared/components/commit.vue'; -import mountComponent from '../../helpers/vue_mount_component_helper'; +import { shallowMount } from '@vue/test-utils'; +import CommitComponent from '~/vue_shared/components/commit.vue'; +import Icon from '~/vue_shared/components/icon.vue'; +import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; describe('Commit component', () => { let props; - let component; - let CommitComponent; + let wrapper; - beforeEach(() => { - CommitComponent = Vue.extend(commitComp); - }); + const findUserAvatar = () => wrapper.find(UserAvatarLink); + + const createComponent = propsData => { + wrapper = shallowMount(CommitComponent, { + propsData, + sync: false, + }); + }; afterEach(() => { - component.$destroy(); + wrapper.destroy(); }); it('should render a fork icon if it does not represent a tag', () => { - component = mountComponent(CommitComponent, { + createComponent({ tag: false, commitRef: { name: 'master', @@ -34,7 +39,12 @@ describe('Commit component', () => { }, }); - expect(component.$el.querySelector('.icon-container').children).toContain('svg'); + expect( + wrapper + .find('.icon-container') + .find(Icon) + .exists(), + ).toBe(true); }); describe('Given all the props', () => { @@ -56,68 +66,51 @@ describe('Commit component', () => { username: 'jschatz1', }, }; - - component = mountComponent(CommitComponent, props); + createComponent(props); }); it('should render a tag icon if it represents a tag', () => { - expect(component.$el.querySelector('.icon-container svg.ic-tag')).not.toBeNull(); + expect(wrapper.find('icon-stub[name="tag"]').exists()).toBe(true); }); it('should render a link to the ref url', () => { - expect(component.$el.querySelector('.ref-name').getAttribute('href')).toEqual( - props.commitRef.ref_url, - ); + expect(wrapper.find('.ref-name').attributes('href')).toBe(props.commitRef.ref_url); }); it('should render the ref name', () => { - expect(component.$el.querySelector('.ref-name').textContent).toContain(props.commitRef.name); + expect(wrapper.find('.ref-name').text()).toContain(props.commitRef.name); }); it('should render the commit short sha with a link to the commit url', () => { - expect(component.$el.querySelector('.commit-sha').getAttribute('href')).toEqual( - props.commitUrl, - ); + expect(wrapper.find('.commit-sha').attributes('href')).toEqual(props.commitUrl); - expect(component.$el.querySelector('.commit-sha').textContent).toContain(props.shortSha); + expect(wrapper.find('.commit-sha').text()).toContain(props.shortSha); }); it('should render icon for commit', () => { - expect( - component.$el.querySelector('.js-commit-icon use').getAttribute('xlink:href'), - ).toContain('commit'); + expect(wrapper.find('icon-stub[name="commit"]').exists()).toBe(true); }); describe('Given commit title and author props', () => { it('should render a link to the author profile', () => { - expect( - component.$el.querySelector('.commit-title .avatar-image-container').getAttribute('href'), - ).toEqual(props.author.path); + const userAvatar = findUserAvatar(); + + expect(userAvatar.props('linkHref')).toBe(props.author.path); }); it('Should render the author avatar with title and alt attributes', () => { - expect( - component.$el - .querySelector('.commit-title .avatar-image-container .js-user-avatar-image-toolip') - .textContent.trim(), - ).toContain(props.author.username); - - expect( - component.$el - .querySelector('.commit-title .avatar-image-container img') - .getAttribute('alt'), - ).toContain(`${props.author.username}'s avatar`); + const userAvatar = findUserAvatar(); + + expect(userAvatar.exists()).toBe(true); + + expect(userAvatar.props('imgAlt')).toBe(`${props.author.username}'s avatar`); }); }); it('should render the commit title', () => { - expect(component.$el.querySelector('a.commit-row-message').getAttribute('href')).toEqual( - props.commitUrl, - ); + expect(wrapper.find('.commit-row-message').attributes('href')).toEqual(props.commitUrl); - expect(component.$el.querySelector('a.commit-row-message').textContent).toContain( - props.title, - ); + expect(wrapper.find('.commit-row-message').text()).toContain(props.title); }); }); @@ -136,9 +129,9 @@ describe('Commit component', () => { author: {}, }; - component = mountComponent(CommitComponent, props); + createComponent(props); - expect(component.$el.querySelector('.commit-title span').textContent).toContain( + expect(wrapper.find('.commit-title span').text()).toContain( "Can't find HEAD commit for this branch", ); }); @@ -159,16 +152,16 @@ describe('Commit component', () => { author: {}, }; - component = mountComponent(CommitComponent, props); - const refEl = component.$el.querySelector('.ref-name'); + createComponent(props); + const refEl = wrapper.find('.ref-name'); - expect(refEl.textContent).toContain('master'); + expect(refEl.text()).toContain('master'); - expect(refEl.href).toBe(props.commitRef.ref_url); + expect(refEl.attributes('href')).toBe(props.commitRef.ref_url); - expect(refEl.getAttribute('data-original-title')).toBe(props.commitRef.name); + expect(refEl.attributes('data-original-title')).toBe(props.commitRef.name); - expect(component.$el.querySelector('.icon-container .ic-branch')).not.toBeNull(); + expect(wrapper.find('icon-stub[name="branch"]').exists()).toBe(true); }); }); @@ -192,16 +185,16 @@ describe('Commit component', () => { author: {}, }; - component = mountComponent(CommitComponent, props); - const refEl = component.$el.querySelector('.ref-name'); + createComponent(props); + const refEl = wrapper.find('.ref-name'); - expect(refEl.textContent).toContain('1234'); + expect(refEl.text()).toContain('1234'); - expect(refEl.href).toBe(props.mergeRequestRef.path); + expect(refEl.attributes('href')).toBe(props.mergeRequestRef.path); - expect(refEl.getAttribute('data-original-title')).toBe(props.mergeRequestRef.title); + expect(refEl.attributes('data-original-title')).toBe(props.mergeRequestRef.title); - expect(component.$el.querySelector('.icon-container .ic-git-merge')).not.toBeNull(); + expect(wrapper.find('icon-stub[name="git-merge"]').exists()).toBe(true); }); }); @@ -226,9 +219,9 @@ describe('Commit component', () => { showRefInfo: false, }; - component = mountComponent(CommitComponent, props); + createComponent(props); - expect(component.$el.querySelector('.ref-name')).toBeNull(); + expect(wrapper.find('.ref-name').exists()).toBe(false); }); }); }); diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js new file mode 100644 index 00000000000..2f87359a4a6 --- /dev/null +++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js @@ -0,0 +1,108 @@ +import { shallowMount } from '@vue/test-utils'; +import { placeholderImage } from '~/lazy_loader'; +import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue'; +import defaultAvatarUrl from 'images/no_avatar.png'; + +jest.mock('images/no_avatar.png', () => 'default-avatar-url'); + +const DEFAULT_PROPS = { + size: 99, + imgSrc: 'myavatarurl.com', + imgAlt: 'mydisplayname', + cssClasses: 'myextraavatarclass', + tooltipText: 'tooltip text', + tooltipPlacement: 'bottom', +}; + +describe('User Avatar Image Component', () => { + let wrapper; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('Initialization', () => { + beforeEach(() => { + wrapper = shallowMount(UserAvatarImage, { + propsData: { + ...DEFAULT_PROPS, + }, + sync: false, + }); + }); + + it('should have <img> as a child element', () => { + const imageElement = wrapper.find('img'); + + expect(imageElement.exists()).toBe(true); + expect(imageElement.attributes('src')).toBe(`${DEFAULT_PROPS.imgSrc}?width=99`); + expect(imageElement.attributes('data-src')).toBe(`${DEFAULT_PROPS.imgSrc}?width=99`); + expect(imageElement.attributes('alt')).toBe(DEFAULT_PROPS.imgAlt); + }); + + it('should properly render img css', () => { + const classes = wrapper.find('img').classes(); + expect(classes).toEqual(expect.arrayContaining(['avatar', 's99', DEFAULT_PROPS.cssClasses])); + expect(classes).not.toContain('lazy'); + }); + }); + + describe('Initialization when lazy', () => { + beforeEach(() => { + wrapper = shallowMount(UserAvatarImage, { + propsData: { + ...DEFAULT_PROPS, + lazy: true, + }, + sync: false, + }); + }); + + it('should add lazy attributes', () => { + const imageElement = wrapper.find('img'); + + expect(imageElement.classes()).toContain('lazy'); + expect(imageElement.attributes('src')).toBe(placeholderImage); + expect(imageElement.attributes('data-src')).toBe(`${DEFAULT_PROPS.imgSrc}?width=99`); + }); + }); + + describe('Initialization without src', () => { + beforeEach(() => { + wrapper = shallowMount(UserAvatarImage, { sync: false }); + }); + + it('should have default avatar image', () => { + const imageElement = wrapper.find('img'); + + expect(imageElement.attributes('src')).toBe(`${defaultAvatarUrl}?width=20`); + }); + }); + + describe('dynamic tooltip content', () => { + const props = DEFAULT_PROPS; + const slots = { + default: ['Action!'], + }; + + beforeEach(() => { + wrapper = shallowMount(UserAvatarImage, { propsData: { props }, slots, sync: false }); + }); + + it('renders the tooltip slot', () => { + expect(wrapper.find('.js-user-avatar-image-toolip').exists()).toBe(true); + }); + + it('renders the tooltip content', () => { + expect(wrapper.find('.js-user-avatar-image-toolip').text()).toContain(slots.default[0]); + }); + + it('does not render tooltip data attributes for on avatar image', () => { + const avatarImg = wrapper.find('img'); + + expect(avatarImg.attributes('data-original-title')).toBeFalsy(); + expect(avatarImg.attributes('data-placement')).not.toBeDefined(); + expect(avatarImg.attributes('data-container')).not.toBeDefined(); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js new file mode 100644 index 00000000000..fc2eb6329b0 --- /dev/null +++ b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js @@ -0,0 +1,186 @@ +import UserPopover from '~/vue_shared/components/user_popover/user_popover.vue'; +import { mount } from '@vue/test-utils'; + +const DEFAULT_PROPS = { + loaded: true, + user: { + username: 'root', + name: 'Administrator', + location: 'Vienna', + bio: null, + organization: null, + status: null, + }, +}; + +describe('User Popover Component', () => { + const fixtureTemplate = 'merge_requests/diff_comment.html'; + preloadFixtures(fixtureTemplate); + + let wrapper; + + beforeEach(() => { + loadFixtures(fixtureTemplate); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('Empty', () => { + beforeEach(() => { + wrapper = mount(UserPopover, { + propsData: { + target: document.querySelector('.js-user-link'), + user: { + name: null, + username: null, + location: null, + bio: null, + organization: null, + status: null, + }, + }, + sync: false, + }); + }); + + it('should return skeleton loaders', () => { + expect(wrapper.findAll('.animation-container').length).toBe(4); + }); + }); + + describe('basic data', () => { + it('should show basic fields', () => { + wrapper = mount(UserPopover, { + propsData: { + ...DEFAULT_PROPS, + target: document.querySelector('.js-user-link'), + }, + sync: false, + }); + + expect(wrapper.text()).toContain(DEFAULT_PROPS.user.name); + expect(wrapper.text()).toContain(DEFAULT_PROPS.user.username); + expect(wrapper.text()).toContain(DEFAULT_PROPS.user.location); + }); + + it('shows icon for location', () => { + const iconEl = wrapper.find('.js-location svg'); + + expect(iconEl.find('use').element.getAttribute('xlink:href')).toContain('location'); + }); + }); + + describe('job data', () => { + it('should show only bio if no organization is available', () => { + const testProps = Object.assign({}, DEFAULT_PROPS); + testProps.user.bio = 'Engineer'; + + wrapper = mount(UserPopover, { + propsData: { + ...testProps, + target: document.querySelector('.js-user-link'), + }, + sync: false, + }); + + expect(wrapper.text()).toContain('Engineer'); + }); + + it('should show only organization if no bio is available', () => { + const testProps = Object.assign({}, DEFAULT_PROPS); + testProps.user.organization = 'GitLab'; + + wrapper = mount(UserPopover, { + propsData: { + ...testProps, + target: document.querySelector('.js-user-link'), + }, + sync: false, + }); + + expect(wrapper.text()).toContain('GitLab'); + }); + + it('should display bio and organization in separate lines', () => { + const testProps = Object.assign({}, DEFAULT_PROPS); + testProps.user.bio = 'Engineer'; + testProps.user.organization = 'GitLab'; + + wrapper = mount(UserPopover, { + propsData: { + ...DEFAULT_PROPS, + target: document.querySelector('.js-user-link'), + }, + sync: false, + }); + + expect(wrapper.find('.js-bio').text()).toContain('Engineer'); + expect(wrapper.find('.js-organization').text()).toContain('GitLab'); + }); + + it('should not encode special characters in bio and organization', () => { + const testProps = Object.assign({}, DEFAULT_PROPS); + testProps.user.bio = 'Manager & Team Lead'; + testProps.user.organization = 'Me & my <funky> Company'; + + wrapper = mount(UserPopover, { + propsData: { + ...DEFAULT_PROPS, + target: document.querySelector('.js-user-link'), + }, + sync: false, + }); + + expect(wrapper.find('.js-bio').text()).toContain('Manager & Team Lead'); + expect(wrapper.find('.js-organization').text()).toContain('Me & my <funky> Company'); + }); + + it('shows icon for bio', () => { + const iconEl = wrapper.find('.js-bio svg'); + + expect(iconEl.find('use').element.getAttribute('xlink:href')).toContain('profile'); + }); + + it('shows icon for organization', () => { + const iconEl = wrapper.find('.js-organization svg'); + + expect(iconEl.find('use').element.getAttribute('xlink:href')).toContain('work'); + }); + }); + + describe('status data', () => { + it('should show only message', () => { + const testProps = Object.assign({}, DEFAULT_PROPS); + testProps.user.status = { message_html: 'Hello World' }; + + wrapper = mount(UserPopover, { + propsData: { + ...DEFAULT_PROPS, + target: document.querySelector('.js-user-link'), + }, + sync: false, + }); + + expect(wrapper.text()).toContain('Hello World'); + }); + + it('should show message and emoji', () => { + const testProps = Object.assign({}, DEFAULT_PROPS); + testProps.user.status = { emoji: 'basketball_player', message_html: 'Hello World' }; + + wrapper = mount(UserPopover, { + propsData: { + ...DEFAULT_PROPS, + target: document.querySelector('.js-user-link'), + status: { emoji: 'basketball_player', message_html: 'Hello World' }, + }, + sync: false, + }); + + expect(wrapper.text()).toContain('Hello World'); + expect(wrapper.html()).toContain('<gl-emoji data-name="basketball_player"'); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_image_spec.js b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_image_spec.js deleted file mode 100644 index c5045afc5b0..00000000000 --- a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_image_spec.js +++ /dev/null @@ -1,120 +0,0 @@ -import Vue from 'vue'; -import { placeholderImage } from '~/lazy_loader'; -import userAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue'; -import mountComponent, { mountComponentWithSlots } from 'spec/helpers/vue_mount_component_helper'; -import defaultAvatarUrl from '~/../images/no_avatar.png'; - -const DEFAULT_PROPS = { - size: 99, - imgSrc: 'myavatarurl.com', - imgAlt: 'mydisplayname', - cssClasses: 'myextraavatarclass', - tooltipText: 'tooltip text', - tooltipPlacement: 'bottom', -}; - -describe('User Avatar Image Component', function() { - let vm; - let UserAvatarImage; - - beforeEach(() => { - UserAvatarImage = Vue.extend(userAvatarImage); - }); - - describe('Initialization', function() { - beforeEach(function() { - vm = mountComponent(UserAvatarImage, { - ...DEFAULT_PROPS, - }).$mount(); - }); - - it('should return a defined Vue component', function() { - expect(vm).toBeDefined(); - }); - - it('should have <img> as a child element', function() { - const imageElement = vm.$el.querySelector('img'); - - expect(imageElement).not.toBe(null); - expect(imageElement.getAttribute('src')).toBe(`${DEFAULT_PROPS.imgSrc}?width=99`); - expect(imageElement.getAttribute('data-src')).toBe(`${DEFAULT_PROPS.imgSrc}?width=99`); - expect(imageElement.getAttribute('alt')).toBe(DEFAULT_PROPS.imgAlt); - }); - - it('should properly compute avatarSizeClass', function() { - expect(vm.avatarSizeClass).toBe('s99'); - }); - - it('should properly render img css', function() { - const { classList } = vm.$el.querySelector('img'); - const containsAvatar = classList.contains('avatar'); - const containsSizeClass = classList.contains('s99'); - const containsCustomClass = classList.contains(DEFAULT_PROPS.cssClasses); - const lazyClass = classList.contains('lazy'); - - expect(containsAvatar).toBe(true); - expect(containsSizeClass).toBe(true); - expect(containsCustomClass).toBe(true); - expect(lazyClass).toBe(false); - }); - }); - - describe('Initialization when lazy', function() { - beforeEach(function() { - vm = mountComponent(UserAvatarImage, { - ...DEFAULT_PROPS, - lazy: true, - }).$mount(); - }); - - it('should add lazy attributes', function() { - const imageElement = vm.$el.querySelector('img'); - const lazyClass = imageElement.classList.contains('lazy'); - - expect(lazyClass).toBe(true); - expect(imageElement.getAttribute('src')).toBe(placeholderImage); - expect(imageElement.getAttribute('data-src')).toBe(`${DEFAULT_PROPS.imgSrc}?width=99`); - }); - }); - - describe('Initialization without src', function() { - beforeEach(function() { - vm = mountComponent(UserAvatarImage); - }); - - it('should have default avatar image', function() { - const imageElement = vm.$el.querySelector('img'); - - expect(imageElement.getAttribute('src')).toBe(defaultAvatarUrl); - }); - }); - - describe('dynamic tooltip content', () => { - const props = DEFAULT_PROPS; - const slots = { - default: ['Action!'], - }; - - beforeEach(() => { - vm = mountComponentWithSlots(UserAvatarImage, { props, slots }).$mount(); - }); - - it('renders the tooltip slot', () => { - expect(vm.$el.querySelector('.js-user-avatar-image-toolip')).not.toBe(null); - }); - - it('renders the tooltip content', () => { - expect(vm.$el.querySelector('.js-user-avatar-image-toolip').textContent).toContain( - slots.default[0], - ); - }); - - it('does not render tooltip data attributes for on avatar image', () => { - const avatarImg = vm.$el.querySelector('img'); - - expect(avatarImg.dataset.originalTitle).not.toBeDefined(); - expect(avatarImg.dataset.placement).not.toBeDefined(); - expect(avatarImg.dataset.container).not.toBeDefined(); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js deleted file mode 100644 index c7e0d806d80..00000000000 --- a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js +++ /dev/null @@ -1,167 +0,0 @@ -import Vue from 'vue'; -import userPopover from '~/vue_shared/components/user_popover/user_popover.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -const DEFAULT_PROPS = { - loaded: true, - user: { - username: 'root', - name: 'Administrator', - location: 'Vienna', - bio: null, - organization: null, - status: null, - }, -}; - -const UserPopover = Vue.extend(userPopover); - -describe('User Popover Component', () => { - const fixtureTemplate = 'merge_requests/diff_comment.html'; - preloadFixtures(fixtureTemplate); - - let vm; - - beforeEach(() => { - loadFixtures(fixtureTemplate); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('Empty', () => { - beforeEach(() => { - vm = mountComponent(UserPopover, { - target: document.querySelector('.js-user-link'), - user: { - name: null, - username: null, - location: null, - bio: null, - organization: null, - status: null, - }, - }); - }); - - it('should return skeleton loaders', () => { - expect(vm.$el.querySelectorAll('.animation-container').length).toBe(4); - }); - }); - - describe('basic data', () => { - it('should show basic fields', () => { - vm = mountComponent(UserPopover, { - ...DEFAULT_PROPS, - target: document.querySelector('.js-user-link'), - }); - - expect(vm.$el.textContent).toContain(DEFAULT_PROPS.user.name); - expect(vm.$el.textContent).toContain(DEFAULT_PROPS.user.username); - expect(vm.$el.textContent).toContain(DEFAULT_PROPS.user.location); - }); - - it('shows icon for location', () => { - const iconEl = vm.$el.querySelector('.js-location svg'); - - expect(iconEl.querySelector('use').getAttribute('xlink:href')).toContain('location'); - }); - }); - - describe('job data', () => { - it('should show only bio if no organization is available', () => { - const testProps = Object.assign({}, DEFAULT_PROPS); - testProps.user.bio = 'Engineer'; - - vm = mountComponent(UserPopover, { - ...testProps, - target: document.querySelector('.js-user-link'), - }); - - expect(vm.$el.textContent).toContain('Engineer'); - }); - - it('should show only organization if no bio is available', () => { - const testProps = Object.assign({}, DEFAULT_PROPS); - testProps.user.organization = 'GitLab'; - - vm = mountComponent(UserPopover, { - ...testProps, - target: document.querySelector('.js-user-link'), - }); - - expect(vm.$el.textContent).toContain('GitLab'); - }); - - it('should display bio and organization in separate lines', () => { - const testProps = Object.assign({}, DEFAULT_PROPS); - testProps.user.bio = 'Engineer'; - testProps.user.organization = 'GitLab'; - - vm = mountComponent(UserPopover, { - ...DEFAULT_PROPS, - target: document.querySelector('.js-user-link'), - }); - - expect(vm.$el.querySelector('.js-bio').textContent).toContain('Engineer'); - expect(vm.$el.querySelector('.js-organization').textContent).toContain('GitLab'); - }); - - it('should not encode special characters in bio and organization', () => { - const testProps = Object.assign({}, DEFAULT_PROPS); - testProps.user.bio = 'Manager & Team Lead'; - testProps.user.organization = 'Me & my <funky> Company'; - - vm = mountComponent(UserPopover, { - ...DEFAULT_PROPS, - target: document.querySelector('.js-user-link'), - }); - - expect(vm.$el.querySelector('.js-bio').textContent).toContain('Manager & Team Lead'); - expect(vm.$el.querySelector('.js-organization').textContent).toContain( - 'Me & my <funky> Company', - ); - }); - - it('shows icon for bio', () => { - const iconEl = vm.$el.querySelector('.js-bio svg'); - - expect(iconEl.querySelector('use').getAttribute('xlink:href')).toContain('profile'); - }); - - it('shows icon for organization', () => { - const iconEl = vm.$el.querySelector('.js-organization svg'); - - expect(iconEl.querySelector('use').getAttribute('xlink:href')).toContain('work'); - }); - }); - - describe('status data', () => { - it('should show only message', () => { - const testProps = Object.assign({}, DEFAULT_PROPS); - testProps.user.status = { message_html: 'Hello World' }; - - vm = mountComponent(UserPopover, { - ...DEFAULT_PROPS, - target: document.querySelector('.js-user-link'), - }); - - expect(vm.$el.textContent).toContain('Hello World'); - }); - - it('should show message and emoji', () => { - const testProps = Object.assign({}, DEFAULT_PROPS); - testProps.user.status = { emoji: 'basketball_player', message_html: 'Hello World' }; - - vm = mountComponent(UserPopover, { - ...DEFAULT_PROPS, - target: document.querySelector('.js-user-link'), - status: { emoji: 'basketball_player', message_html: 'Hello World' }, - }); - - expect(vm.$el.textContent).toContain('Hello World'); - expect(vm.$el.innerHTML).toContain('<gl-emoji data-name="basketball_player"'); - }); - }); -}); diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 88a7fbea1d1..1269fac7f48 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' include ImportExport::CommonUtil describe Gitlab::ImportExport::ProjectTreeRestorer do + include ImportExport::CommonUtil + let(:shared) { project.import_export_shared } describe 'restore project tree' do @@ -16,7 +18,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do RSpec::Mocks.with_temporary_scope do @project = create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project') @shared = @project.import_export_shared - allow(@shared).to receive(:export_path).and_return('spec/fixtures/lib/gitlab/import_export/') + + setup_import_export_config('complex') allow_any_instance_of(Repository).to receive(:fetch_source_branch!).and_return(true) allow_any_instance_of(Gitlab::Git::Repository).to receive(:branch_exists?).and_return(false) @@ -257,9 +260,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end it 'has the correct number of pipelines and statuses' do - expect(@project.ci_pipelines.size).to eq(5) + expect(@project.ci_pipelines.size).to eq(6) - @project.ci_pipelines.zip([2, 2, 2, 2, 2]) + @project.ci_pipelines.zip([0, 2, 2, 2, 2, 2]) .each do |(pipeline, expected_status_size)| expect(pipeline.statuses.size).to eq(expected_status_size) end @@ -268,7 +271,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do context 'when restoring hierarchy of pipeline, stages and jobs' do it 'restores pipelines' do - expect(Ci::Pipeline.all.count).to be 5 + expect(Ci::Pipeline.all.count).to be 6 end it 'restores pipeline stages' do @@ -314,21 +317,33 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end end - context 'Light JSON' do + context 'project.json file access check' do let(:user) { create(:user) } let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') } let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) } let(:restored_project_json) { project_tree_restorer.restore } - before do - allow(shared).to receive(:export_path).and_return('spec/fixtures/lib/gitlab/import_export/') + it 'does not read a symlink' do + Dir.mktmpdir do |tmpdir| + setup_symlink(tmpdir, 'project.json') + allow(shared).to receive(:export_path).and_call_original + + expect(project_tree_restorer.restore).to eq(false) + expect(shared.errors).to include('Incorrect JSON format') + end end + end + + context 'Light JSON' do + let(:user) { create(:user) } + let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') } + let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) } + let(:restored_project_json) { project_tree_restorer.restore } context 'with a simple project' do before do - project_tree_restorer.instance_variable_set(:@path, "spec/fixtures/lib/gitlab/import_export/project.light.json") - - restored_project_json + setup_import_export_config('light') + expect(restored_project_json).to eq(true) end it_behaves_like 'restores project correctly', @@ -339,19 +354,6 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do first_issue_labels: 1, services: 1 - context 'project.json file access check' do - it 'does not read a symlink' do - Dir.mktmpdir do |tmpdir| - setup_symlink(tmpdir, 'project.json') - allow(shared).to receive(:export_path).and_call_original - - restored_project_json - - expect(shared.errors).to be_empty - end - end - end - context 'when there is an existing build with build token' do before do create(:ci_build, token: 'abcd') @@ -367,6 +369,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end context 'when the project has overridden params in import data' do + before do + setup_import_export_config('light') + end + it 'handles string versions of visibility_level' do # Project needs to be in a group for visibility level comparison # to happen @@ -375,24 +381,21 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do project.create_import_data(data: { override_params: { visibility_level: Gitlab::VisibilityLevel::INTERNAL.to_s } }) - restored_project_json - + expect(restored_project_json).to eq(true) expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) end it 'overwrites the params stored in the JSON' do project.create_import_data(data: { override_params: { description: "Overridden" } }) - restored_project_json - + expect(restored_project_json).to eq(true) expect(project.description).to eq("Overridden") end it 'does not allow setting params that are excluded from import_export settings' do project.create_import_data(data: { override_params: { lfs_enabled: true } }) - restored_project_json - + expect(restored_project_json).to eq(true) expect(project.lfs_enabled).to be_falsey end @@ -408,7 +411,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do project.create_import_data(data: { override_params: disabled_access_levels }) - restored_project_json + expect(restored_project_json).to eq(true) aggregate_failures do access_level_keys.each do |key| @@ -429,9 +432,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end before do - project_tree_restorer.instance_variable_set(:@path, "spec/fixtures/lib/gitlab/import_export/project.group.json") - - restored_project_json + setup_import_export_config('group') + expect(restored_project_json).to eq(true) end it_behaves_like 'restores project correctly', @@ -463,11 +465,11 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end before do - project_tree_restorer.instance_variable_set(:@path, "spec/fixtures/lib/gitlab/import_export/project.light.json") + setup_import_export_config('light') end it 'does not import any templated services' do - restored_project_json + expect(restored_project_json).to eq(true) expect(project.services.where(template: true).count).to eq(0) end @@ -477,8 +479,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do expect_any_instance_of(Gitlab::ImportExport::Shared).not_to receive(:error) - restored_project_json - + expect(restored_project_json).to eq(true) expect(project.labels.count).to eq(1) end @@ -487,8 +488,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do expect_any_instance_of(Gitlab::ImportExport::Shared).not_to receive(:error) - restored_project_json - + expect(restored_project_json).to eq(true) expect(project.group.milestones.count).to eq(1) expect(project.milestones.count).to eq(0) end @@ -504,13 +504,14 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do group: create(:group)) end - it 'preserves the project milestone IID' do - project_tree_restorer.instance_variable_set(:@path, "spec/fixtures/lib/gitlab/import_export/project.milestone-iid.json") + before do + setup_import_export_config('milestone-iid') + end + it 'preserves the project milestone IID' do expect_any_instance_of(Gitlab::ImportExport::Shared).not_to receive(:error) - restored_project_json - + expect(restored_project_json).to eq(true) expect(project.milestones.count).to eq(2) expect(Milestone.find_by_title('Another milestone').iid).to eq(1) expect(Milestone.find_by_title('Group-level milestone').iid).to eq(2) @@ -518,19 +519,21 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end context 'with external authorization classification labels' do + before do + setup_import_export_config('light') + end + it 'converts empty external classification authorization labels to nil' do project.create_import_data(data: { override_params: { external_authorization_classification_label: "" } }) - restored_project_json - + expect(restored_project_json).to eq(true) expect(project.external_authorization_classification_label).to be_nil end it 'preserves valid external classification authorization labels' do project.create_import_data(data: { override_params: { external_authorization_classification_label: "foobar" } }) - restored_project_json - + expect(restored_project_json).to eq(true) expect(project.external_authorization_classification_label).to eq("foobar") end end diff --git a/spec/lib/gitlab/import_export/relation_rename_service_spec.rb b/spec/lib/gitlab/import_export/relation_rename_service_spec.rb index 472bf55d37e..843de27df1a 100644 --- a/spec/lib/gitlab/import_export/relation_rename_service_spec.rb +++ b/spec/lib/gitlab/import_export/relation_rename_service_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe Gitlab::ImportExport::RelationRenameService do + include ImportExport::CommonUtil + let(:renames) do { 'example_relation1' => 'new_example_relation1', @@ -21,12 +23,12 @@ describe Gitlab::ImportExport::RelationRenameService do context 'when importing' do let(:project_tree_restorer) { Gitlab::ImportExport::ProjectTreeRestorer.new(user: user, shared: shared, project: project) } - let(:import_path) { 'spec/fixtures/lib/gitlab/import_export' } - let(:file_content) { IO.read("#{import_path}/project.json") } - let!(:json_file) { ActiveSupport::JSON.decode(file_content) } + let(:file_content) { IO.read(File.join(shared.export_path, 'project.json')) } + let(:json_file) { ActiveSupport::JSON.decode(file_content) } before do - allow(shared).to receive(:export_path).and_return(import_path) + setup_import_export_config('complex') + allow(ActiveSupport::JSON).to receive(:decode).and_call_original allow(ActiveSupport::JSON).to receive(:decode).with(file_content).and_return(json_file) end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 71b999048b4..052651bdf50 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -3342,22 +3342,6 @@ describe Project do end end - describe '#append_or_update_attribute' do - let(:project) { create(:project) } - - it 'shows full error updating an invalid MR' do - expect { project.append_or_update_attribute(:merge_requests, [create(:merge_request)]) } - .to raise_error(ActiveRecord::RecordInvalid, /Failed to set merge_requests:/) - end - - it 'updates the project successfully' do - merge_request = create(:merge_request, target_project: project, source_project: project) - - expect { project.append_or_update_attribute(:merge_requests, [merge_request]) } - .not_to raise_error - end - end - describe '#update' do let(:project) { create(:project) } diff --git a/spec/support/import_export/common_util.rb b/spec/support/import_export/common_util.rb index ac6840dbcfc..4e149c9fa54 100644 --- a/spec/support/import_export/common_util.rb +++ b/spec/support/import_export/common_util.rb @@ -8,5 +8,12 @@ module ImportExport File.open("#{tmpdir}/test", 'w') { |file| file.write("test") } FileUtils.ln_s("#{tmpdir}/test", "#{tmpdir}/#{symlink_name}") end + + def setup_import_export_config(name, prefix = nil) + export_path = [prefix, 'spec', 'fixtures', 'lib', 'gitlab', 'import_export', name].compact + export_path = File.join(*export_path) + + allow_any_instance_of(Gitlab::ImportExport).to receive(:export_path) { export_path } + end end end diff --git a/spec/support/shared_examples/models/concern/issuable_shared_examples.rb b/spec/support/shared_examples/models/concern/issuable_shared_examples.rb index 4ebb5e35e0e..4978a403324 100644 --- a/spec/support/shared_examples/models/concern/issuable_shared_examples.rb +++ b/spec/support/shared_examples/models/concern/issuable_shared_examples.rb @@ -4,7 +4,7 @@ shared_examples_for 'matches_cross_reference_regex? fails fast' do it 'fails fast for long strings' do # took well under 1 second in CI https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/3267#note_172823 expect do - Timeout.timeout(3.seconds) { mentionable.matches_cross_reference_regex? } + Timeout.timeout(6.seconds) { mentionable.matches_cross_reference_regex? } end.not_to raise_error end end |