diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-10 18:11:53 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-10 18:11:53 +0300 |
commit | 0a412bceb98cd7acd46701d75dbad9683cb33baf (patch) | |
tree | 4bb45380d97ae2c12cf101b2f30bcc5a9f114d89 /spec | |
parent | 70eb0cf7518e1ccc2a26d9aaa9fc16e21c6de5f1 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
28 files changed, 298 insertions, 173 deletions
diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb index a4cbf873b0b..1f9c12ecbce 100644 --- a/spec/factories/ci/variables.rb +++ b/spec/factories/ci/variables.rb @@ -5,6 +5,7 @@ FactoryBot.define do sequence(:key) { |n| "VARIABLE_#{n}" } value { 'VARIABLE_VALUE' } masked { false } + variable_type { :env_var } trait(:protected) do add_attribute(:protected) { true } diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 9ba7f809372..f279af90aa3 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -275,7 +275,7 @@ RSpec.describe 'Project issue boards', :js do context 'issue card' do it 'shows assignee' do page.within(find('.board:nth-child(2)')) do - expect(page).to have_selector('.avatar', count: 1) + expect(page).to have_selector('.gl-avatar', count: 1) end end diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index 534da71e39a..2600c00346e 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -268,7 +268,7 @@ RSpec.describe 'Runners' do it 'group runners are not available' do visit project_runners_path(project) - expect(page).not_to have_content 'Group owners can register group runners in the group\'s CI/CD settings.' + expect(page).not_to have_content 'To register them, go to the group\'s Runners page.' expect(page).to have_content 'Ask your group owner to set up a group runner' end end @@ -287,7 +287,7 @@ RSpec.describe 'Runners' do expect(page).to have_content 'This group does not have any group runners yet.' - expect(page).to have_content 'Group owners can register group runners in the group\'s CI/CD settings.' + expect(page).to have_content 'To register them, go to the group\'s Runners page.' expect(page).not_to have_content 'Ask your group owner to set up a group runner' end end @@ -313,7 +313,7 @@ RSpec.describe 'Runners' do expect(page).to have_content 'This group does not have any group runners yet.' - expect(page).not_to have_content 'Group owners can register group runners in the group\'s CI/CD settings.' + expect(page).not_to have_content 'To register them, go to the group\'s Runners page.' expect(page).to have_content 'Ask your group owner to set up a group runner.' end end diff --git a/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js b/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js index 9b93fd26fa0..bffadbde087 100644 --- a/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js +++ b/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js @@ -87,7 +87,7 @@ describe('AddContextCommitsModal', () => { it('enabled ok button when atleast one row is selected', async () => { wrapper.vm.$store.state.selectedCommits = [{ ...commit, isSelected: true }]; await nextTick(); - expect(findModal().attributes('ok-disabled')).toBeFalsy(); + expect(findModal().attributes('ok-disabled')).toBe(undefined); }); }); @@ -102,7 +102,7 @@ describe('AddContextCommitsModal', () => { it('an enabled ok button when atleast one row is selected', async () => { wrapper.vm.$store.state.selectedCommits = [{ ...commit, isSelected: true }]; await nextTick(); - expect(findModal().attributes('ok-disabled')).toBeFalsy(); + expect(findModal().attributes('ok-disabled')).toBe(undefined); }); it('a disabled ok button in first tab, when row is selected in second tab', () => { diff --git a/spec/frontend/boards/board_card_inner_spec.js b/spec/frontend/boards/board_card_inner_spec.js index c6de3ee69f3..985902b4a3b 100644 --- a/spec/frontend/boards/board_card_inner_spec.js +++ b/spec/frontend/boards/board_card_inner_spec.js @@ -238,7 +238,7 @@ describe('Board card component', () => { }); it('renders assignee', () => { - expect(wrapper.find('.board-card-assignee .avatar').exists()).toBe(true); + expect(wrapper.find('.board-card-assignee .gl-avatar').exists()).toBe(true); }); it('sets title', () => { @@ -336,7 +336,7 @@ describe('Board card component', () => { }); it('renders all three assignees', () => { - expect(wrapper.findAll('.board-card-assignee .avatar').length).toEqual(3); + expect(wrapper.findAll('.board-card-assignee .gl-avatar').length).toEqual(3); }); describe('more than three assignees', () => { @@ -362,7 +362,7 @@ describe('Board card component', () => { }); it('renders two assignees', () => { - expect(wrapper.findAll('.board-card-assignee .avatar').length).toEqual(2); + expect(wrapper.findAll('.board-card-assignee .gl-avatar').length).toEqual(2); }); it('renders 99+ avatar counter', async () => { diff --git a/spec/frontend/integrations/overrides/components/integration_overrides_spec.js b/spec/frontend/integrations/overrides/components/integration_overrides_spec.js index 6aa3e661677..12d5674a705 100644 --- a/spec/frontend/integrations/overrides/components/integration_overrides_spec.js +++ b/spec/frontend/integrations/overrides/components/integration_overrides_spec.js @@ -90,7 +90,7 @@ describe('IntegrationOverrides', () => { const table = findGlTable(); expect(table.exists()).toBe(true); - expect(table.attributes('busy')).toBeFalsy(); + expect(table.attributes('busy')).toBeUndefined(); }); it('renders IntegrationTabs with count', async () => { diff --git a/spec/frontend/notes/deprecated_notes_spec.js b/spec/frontend/notes/deprecated_notes_spec.js index 9378f4feb0c..d5e2a189afe 100644 --- a/spec/frontend/notes/deprecated_notes_spec.js +++ b/spec/frontend/notes/deprecated_notes_spec.js @@ -508,7 +508,7 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => { notes.putEditFormInPlace($el); - expect(notes.glForm.enableGFM).toBeTruthy(); + expect(notes.glForm.enableGFM).toBe(''); }); }); @@ -781,21 +781,21 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => { const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this'; const hasQuickActions = notes.hasQuickActions(sampleComment); - expect(hasQuickActions).toBeTruthy(); + expect(hasQuickActions).toBe(true); }); it('should return false when comment does NOT begin with a quick action', () => { const sampleComment = 'Hey, /unassign Merging this'; const hasQuickActions = notes.hasQuickActions(sampleComment); - expect(hasQuickActions).toBeFalsy(); + expect(hasQuickActions).toBe(false); }); it('should return false when comment does NOT have any quick actions', () => { const sampleComment = 'Looking good, Awesome!'; const hasQuickActions = notes.hasQuickActions(sampleComment); - expect(hasQuickActions).toBeFalsy(); + expect(hasQuickActions).toBe(false); }); }); @@ -885,14 +885,14 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => { expect($tempNote.prop('nodeName')).toEqual('LI'); expect($tempNote.attr('id')).toEqual(uniqueId); - expect($tempNote.hasClass('being-posted')).toBeTruthy(); - expect($tempNote.hasClass('fade-in-half')).toBeTruthy(); + expect($tempNote.hasClass('being-posted')).toBe(true); + expect($tempNote.hasClass('fade-in-half')).toBe(true); $tempNote.find('.timeline-icon > a, .note-header-info > a').each((i, el) => { expect(el.getAttribute('href')).toEqual(`/${currentUsername}`); }); expect($tempNote.find('.timeline-icon .avatar').attr('src')).toEqual(currentUserAvatar); - expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeFalsy(); + expect($tempNote.find('.timeline-content').hasClass('discussion')).toBe(false); expect($tempNoteHeader.find('.d-none.d-sm-inline-block').text().trim()).toEqual( currentUserFullname, ); @@ -914,7 +914,7 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => { }); expect($tempNote.prop('nodeName')).toEqual('LI'); - expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeTruthy(); + expect($tempNote.find('.timeline-content').hasClass('discussion')).toBe(true); }); it('should return a escaped user name', () => { @@ -952,8 +952,8 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => { expect($tempNote.prop('nodeName')).toEqual('LI'); expect($tempNote.attr('id')).toEqual(uniqueId); - expect($tempNote.hasClass('being-posted')).toBeTruthy(); - expect($tempNote.hasClass('fade-in-half')).toBeTruthy(); + expect($tempNote.hasClass('being-posted')).toBe(true); + expect($tempNote.hasClass('fade-in-half')).toBe(true); expect($tempNote.find('.timeline-content i').text().trim()).toEqual(sampleCommandDescription); }); }); diff --git a/spec/frontend/pages/projects/graphs/code_coverage_spec.js b/spec/frontend/pages/projects/graphs/code_coverage_spec.js index 0457a5eb9a9..f272891919d 100644 --- a/spec/frontend/pages/projects/graphs/code_coverage_spec.js +++ b/spec/frontend/pages/projects/graphs/code_coverage_spec.js @@ -163,8 +163,8 @@ describe('Code Coverage', () => { await nextTick(); - expect(findFirstDropdownItem().attributes('ischecked')).toBeFalsy(); - expect(findSecondDropdownItem().attributes('ischecked')).toBeTruthy(); + expect(findFirstDropdownItem().attributes('ischecked')).toBe(undefined); + expect(findSecondDropdownItem().attributes('ischecked')).toBe('true'); }); it('updates the graph data when selecting a different option in dropdown', async () => { diff --git a/spec/frontend/pipeline_editor/components/validate/ci_validate_spec.js b/spec/frontend/pipeline_editor/components/validate/ci_validate_spec.js index f5f01b675b2..b8fbc2b2460 100644 --- a/spec/frontend/pipeline_editor/components/validate/ci_validate_spec.js +++ b/spec/frontend/pipeline_editor/components/validate/ci_validate_spec.js @@ -159,7 +159,7 @@ describe('Pipeline Editor Validate Tab', () => { expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ mutation: lintCIMutation, variables: { - dry_run: true, + dry: true, content: mockCiYml, endpoint: mockCiLintPath, }, @@ -243,7 +243,7 @@ describe('Pipeline Editor Validate Tab', () => { expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ mutation: lintCIMutation, variables: { - dry_run: true, + dry: true, content: 'new yaml content', endpoint: mockCiLintPath, }, diff --git a/spec/frontend/repository/components/blob_content_viewer_spec.js b/spec/frontend/repository/components/blob_content_viewer_spec.js index 3f7d311060c..0f7cf4e61b2 100644 --- a/spec/frontend/repository/components/blob_content_viewer_spec.js +++ b/spec/frontend/repository/components/blob_content_viewer_spec.js @@ -21,12 +21,13 @@ import blobInfoQuery from '~/repository/queries/blob_info.query.graphql'; import userInfoQuery from '~/repository/queries/user_info.query.graphql'; import applicationInfoQuery from '~/repository/queries/application_info.query.graphql'; import CodeIntelligence from '~/code_navigation/components/app.vue'; -import { redirectTo } from '~/lib/utils/url_utility'; +import * as urlUtility from '~/lib/utils/url_utility'; import { isLoggedIn, handleLocationHash } from '~/lib/utils/common_utils'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import httpStatusCodes from '~/lib/utils/http_status'; import LineHighlighter from '~/blob/line_highlighter'; import { LEGACY_FILE_TYPES } from '~/repository/constants'; +import { SIMPLE_BLOB_VIEWER, RICH_BLOB_VIEWER } from '~/blob/components/constants'; import { simpleViewerMock, richViewerMock, @@ -53,7 +54,12 @@ const mockAxios = new MockAdapter(axios); const createMockStore = () => new Vuex.Store({ actions: { fetchData: jest.fn, setInitialData: jest.fn() } }); -const createComponent = async (mockData = {}, mountFn = shallowMount) => { +const mockRouterPush = jest.fn(); +const mockRouter = { + push: mockRouterPush, +}; + +const createComponent = async (mockData = {}, mountFn = shallowMount, mockRoute = {}) => { Vue.use(VueApollo); const { @@ -106,6 +112,10 @@ const createComponent = async (mockData = {}, mountFn = shallowMount) => { apolloProvider: fakeApollo, propsData: propsMock, mixins: [{ data: () => ({ ref: refMock }) }], + mocks: { + $route: mockRoute, + $router: mockRouter, + }, provide: { targetBranch: 'test', originalBranch: 'default-ref', @@ -158,10 +168,11 @@ describe('Blob content viewer component', () => { it('renders a BlobHeader component', async () => { await createComponent(); - expect(findBlobHeader().props('activeViewerType')).toEqual('simple'); + expect(findBlobHeader().props('activeViewerType')).toEqual(SIMPLE_BLOB_VIEWER); expect(findBlobHeader().props('hasRenderError')).toEqual(false); expect(findBlobHeader().props('hideViewerSwitcher')).toEqual(true); expect(findBlobHeader().props('blob')).toEqual(simpleViewerMock); + expect(mockRouterPush).not.toHaveBeenCalled(); }); it('copies blob text to clipboard', async () => { @@ -179,7 +190,7 @@ describe('Blob content viewer component', () => { expect(findBlobContent().props('activeViewer')).toEqual({ fileType: 'text', tooLarge: false, - type: 'simple', + type: SIMPLE_BLOB_VIEWER, renderError: null, }); }); @@ -247,10 +258,11 @@ describe('Blob content viewer component', () => { it('renders a BlobHeader component', async () => { await createComponent({ blob: richViewerMock }); - expect(findBlobHeader().props('activeViewerType')).toEqual('rich'); + expect(findBlobHeader().props('activeViewerType')).toEqual(RICH_BLOB_VIEWER); expect(findBlobHeader().props('hasRenderError')).toEqual(false); expect(findBlobHeader().props('hideViewerSwitcher')).toEqual(false); expect(findBlobHeader().props('blob')).toEqual(richViewerMock); + expect(mockRouterPush).not.toHaveBeenCalled(); }); it('renders a BlobContent component', async () => { @@ -260,30 +272,49 @@ describe('Blob content viewer component', () => { expect(findBlobContent().props('activeViewer')).toEqual({ fileType: 'markup', tooLarge: false, - type: 'rich', + type: RICH_BLOB_VIEWER, renderError: null, }); }); - it('updates viewer type when viewer changed is clicked', async () => { + it('changes to simple viewer when URL has code line hash', async () => { + jest.spyOn(urlUtility, 'getLocationHash').mockReturnValueOnce('L5'); + await createComponent({ blob: richViewerMock }); expect(findBlobContent().props('activeViewer')).toEqual( expect.objectContaining({ - type: 'rich', + type: SIMPLE_BLOB_VIEWER, + }), + ); + expect(findBlobHeader().props('activeViewerType')).toEqual(SIMPLE_BLOB_VIEWER); + }); + + it('updates viewer type when viewer changed is clicked', async () => { + await createComponent({ blob: richViewerMock }, shallowMount, { path: '/mock_path' }); + + expect(findBlobContent().props('activeViewer')).toEqual( + expect.objectContaining({ + type: RICH_BLOB_VIEWER, }), ); - expect(findBlobHeader().props('activeViewerType')).toEqual('rich'); + expect(findBlobHeader().props('activeViewerType')).toEqual(RICH_BLOB_VIEWER); - findBlobHeader().vm.$emit('viewer-changed', 'simple'); + findBlobHeader().vm.$emit('viewer-changed', SIMPLE_BLOB_VIEWER); await nextTick(); - expect(findBlobHeader().props('activeViewerType')).toEqual('simple'); + expect(findBlobHeader().props('activeViewerType')).toEqual(SIMPLE_BLOB_VIEWER); expect(findBlobContent().props('activeViewer')).toEqual( expect.objectContaining({ - type: 'simple', + type: SIMPLE_BLOB_VIEWER, }), ); + expect(mockRouterPush).toHaveBeenCalledWith({ + path: '/mock_path', + query: { + plain: '1', + }, + }); }); }); @@ -503,12 +534,12 @@ describe('Blob content viewer component', () => { it('simple edit redirects to the simple editor', () => { findWebIdeLink().vm.$emit('edit', 'simple'); - expect(redirectTo).toHaveBeenCalledWith(simpleViewerMock.editBlobPath); + expect(urlUtility.redirectTo).toHaveBeenCalledWith(simpleViewerMock.editBlobPath); }); it('IDE edit redirects to the IDE editor', () => { findWebIdeLink().vm.$emit('edit', 'ide'); - expect(redirectTo).toHaveBeenCalledWith(simpleViewerMock.ideEditPath); + expect(urlUtility.redirectTo).toHaveBeenCalledWith(simpleViewerMock.ideEditPath); }); it.each` @@ -543,4 +574,32 @@ describe('Blob content viewer component', () => { }, ); }); + + describe('active viewer based on plain attribute', () => { + it.each` + hasRichViewer | plain | activeViewerType + ${true} | ${'0'} | ${RICH_BLOB_VIEWER} + ${true} | ${'1'} | ${SIMPLE_BLOB_VIEWER} + ${false} | ${'0'} | ${SIMPLE_BLOB_VIEWER} + ${false} | ${'1'} | ${SIMPLE_BLOB_VIEWER} + `( + 'activeViewerType is `$activeViewerType` when hasRichViewer is $hasRichViewer and plain is set to $plain', + async ({ hasRichViewer, plain, activeViewerType }) => { + await createComponent( + { blob: hasRichViewer ? richViewerMock : simpleViewerMock }, + shallowMount, + { query: { plain } }, + ); + + await nextTick(); + + expect(findBlobContent().props('activeViewer')).toEqual( + expect.objectContaining({ + type: activeViewerType, + }), + ); + expect(findBlobHeader().props('activeViewerType')).toEqual(activeViewerType); + }, + ); + }); }); diff --git a/spec/frontend/vue_shared/components/source_viewer/components/chunk_line_spec.js b/spec/frontend/vue_shared/components/source_viewer/components/chunk_line_spec.js index eb2eec92534..9a63f566dec 100644 --- a/spec/frontend/vue_shared/components/source_viewer/components/chunk_line_spec.js +++ b/spec/frontend/vue_shared/components/source_viewer/components/chunk_line_spec.js @@ -1,4 +1,3 @@ -import { GlLink } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import ChunkLine from '~/vue_shared/components/source_viewer/components/chunk_line.vue'; import { @@ -20,7 +19,7 @@ describe('Chunk Line component', () => { wrapper = shallowMountExtended(ChunkLine, { propsData: { ...DEFAULT_PROPS, ...props } }); }; - const findLink = () => wrapper.findComponent(GlLink); + const findLink = () => wrapper.findByTestId('line-number-anchor'); const findContent = () => wrapper.findByTestId('content'); const findWrappedBidiChars = () => wrapper.findAllByTestId('bidi-wrapper'); @@ -50,7 +49,7 @@ describe('Chunk Line component', () => { it('renders a line number', () => { expect(findLink().attributes()).toMatchObject({ 'data-line-number': `${DEFAULT_PROPS.number}`, - to: `#L${DEFAULT_PROPS.number}`, + href: `#L${DEFAULT_PROPS.number}`, id: `L${DEFAULT_PROPS.number}`, }); diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_new_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_new_spec.js index 5e05b54cb8c..f87737ca86a 100644 --- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_new_spec.js +++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_new_spec.js @@ -18,6 +18,8 @@ const PROVIDED_PROPS = { describe('User Avatar Image Component', () => { let wrapper; + const findAvatar = () => wrapper.findComponent(GlAvatar); + afterEach(() => { wrapper.destroy(); }); @@ -28,21 +30,14 @@ describe('User Avatar Image Component', () => { propsData: { ...PROVIDED_PROPS, }, - provide: { - glFeatures: { - glAvatarForAllUserAvatars: true, - }, - }, }); }); it('should render `GlAvatar` and provide correct properties to it', () => { - const avatar = wrapper.findComponent(GlAvatar); - - expect(avatar.attributes('data-src')).toBe( + expect(findAvatar().attributes('data-src')).toBe( `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`, ); - expect(avatar.props()).toMatchObject({ + expect(findAvatar().props()).toMatchObject({ src: `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`, alt: PROVIDED_PROPS.imgAlt, size: PROVIDED_PROPS.size, @@ -63,23 +58,28 @@ describe('User Avatar Image Component', () => { ...PROVIDED_PROPS, lazy: true, }, - provide: { - glFeatures: { - glAvatarForAllUserAvatars: true, - }, - }, }); }); it('should add lazy attributes', () => { - const avatar = wrapper.findComponent(GlAvatar); - - expect(avatar.classes()).toContain('lazy'); - expect(avatar.attributes()).toMatchObject({ + expect(findAvatar().classes()).toContain('lazy'); + expect(findAvatar().attributes()).toMatchObject({ src: placeholderImage, 'data-src': `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`, }); }); + + it('should use maximum number when size is provided as an object', () => { + wrapper = shallowMount(UserAvatarImage, { + propsData: { + ...PROVIDED_PROPS, + size: { default: 16, md: 64, lg: 24 }, + lazy: true, + }, + }); + + expect(findAvatar().attributes('data-src')).toBe(`${PROVIDED_PROPS.imgSrc}?width=${64}`); + }); }); describe('Initialization without src', () => { @@ -89,18 +89,11 @@ describe('User Avatar Image Component', () => { ...PROVIDED_PROPS, imgSrc: null, }, - provide: { - glFeatures: { - glAvatarForAllUserAvatars: true, - }, - }, }); }); it('should have default avatar image', () => { - const avatar = wrapper.findComponent(GlAvatar); - - expect(avatar.props('src')).toBe(`${defaultAvatarUrl}?width=${PROVIDED_PROPS.size}`); + expect(findAvatar().props('src')).toBe(`${defaultAvatarUrl}?width=${PROVIDED_PROPS.size}`); }); }); 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 index 75d2a936b34..6ad2ef226c2 100644 --- 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 @@ -15,47 +15,37 @@ const PROVIDED_PROPS = { describe('User Avatar Image Component', () => { let wrapper; - afterEach(() => { - wrapper.destroy(); - }); - - describe('when `glAvatarForAllUserAvatars` feature flag enabled', () => { - beforeEach(() => { - wrapper = shallowMount(UserAvatarImage, { - propsData: { - ...PROVIDED_PROPS, + const createWrapper = (props = {}, { glAvatarForAllUserAvatars } = {}) => { + wrapper = shallowMount(UserAvatarImage, { + propsData: { + ...PROVIDED_PROPS, + ...props, + }, + provide: { + glFeatures: { + glAvatarForAllUserAvatars, }, - provide: { - glFeatures: { - glAvatarForAllUserAvatars: true, - }, - }, - }); + }, }); + }; - it('should render `UserAvatarImageNew` component', () => { - expect(wrapper.findComponent(UserAvatarImageNew).exists()).toBe(true); - expect(wrapper.findComponent(UserAvatarImageOld).exists()).toBe(false); - }); + afterEach(() => { + wrapper.destroy(); }); - describe('when `glAvatarForAllUserAvatars` feature flag disabled', () => { - beforeEach(() => { - wrapper = shallowMount(UserAvatarImage, { - propsData: { - ...PROVIDED_PROPS, - }, - provide: { - glFeatures: { - glAvatarForAllUserAvatars: false, - }, - }, + describe.each([ + [false, true, true], + [true, false, true], + [true, true, true], + [false, false, false], + ])( + 'when glAvatarForAllUserAvatars=%s and enforceGlAvatar=%s', + (glAvatarForAllUserAvatars, enforceGlAvatar, isUsingNewVersion) => { + it(`will render ${isUsingNewVersion ? 'new' : 'old'} version`, () => { + createWrapper({ enforceGlAvatar }, { glAvatarForAllUserAvatars }); + expect(wrapper.findComponent(UserAvatarImageNew).exists()).toBe(isUsingNewVersion); + expect(wrapper.findComponent(UserAvatarImageOld).exists()).toBe(!isUsingNewVersion); }); - }); - - it('should render `UserAvatarImageOld` component', () => { - expect(wrapper.findComponent(UserAvatarImageNew).exists()).toBe(false); - expect(wrapper.findComponent(UserAvatarImageOld).exists()).toBe(true); - }); - }); + }, + ); }); diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_new_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_new_spec.js index 5ba80b31b99..f485a14cfea 100644 --- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_new_spec.js +++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_new_spec.js @@ -54,6 +54,7 @@ describe('User Avatar Link Component', () => { size: defaultProps.imgSize, tooltipPlacement: defaultProps.tooltipPlacement, tooltipText: '', + enforceGlAvatar: false, }); }); diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_old_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_old_spec.js index 2d513c46e77..cf7a1025dba 100644 --- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_old_spec.js +++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_old_spec.js @@ -54,6 +54,7 @@ describe('User Avatar Link Component', () => { size: defaultProps.imgSize, tooltipPlacement: defaultProps.tooltipPlacement, tooltipText: '', + enforceGlAvatar: false, }); }); diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js index b36b83d1fea..fd3f59008ec 100644 --- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js +++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js @@ -15,47 +15,37 @@ const PROVIDED_PROPS = { describe('User Avatar Link Component', () => { let wrapper; - afterEach(() => { - wrapper.destroy(); - }); - - describe('when `glAvatarForAllUserAvatars` feature flag enabled', () => { - beforeEach(() => { - wrapper = shallowMount(UserAvatarLink, { - propsData: { - ...PROVIDED_PROPS, + const createWrapper = (props = {}, { glAvatarForAllUserAvatars } = {}) => { + wrapper = shallowMount(UserAvatarLink, { + propsData: { + ...PROVIDED_PROPS, + ...props, + }, + provide: { + glFeatures: { + glAvatarForAllUserAvatars, }, - provide: { - glFeatures: { - glAvatarForAllUserAvatars: true, - }, - }, - }); + }, }); + }; - it('should render `UserAvatarLinkNew` component', () => { - expect(wrapper.findComponent(UserAvatarLinkNew).exists()).toBe(true); - expect(wrapper.findComponent(UserAvatarLinkOld).exists()).toBe(false); - }); + afterEach(() => { + wrapper.destroy(); }); - describe('when `glAvatarForAllUserAvatars` feature flag disabled', () => { - beforeEach(() => { - wrapper = shallowMount(UserAvatarLink, { - propsData: { - ...PROVIDED_PROPS, - }, - provide: { - glFeatures: { - glAvatarForAllUserAvatars: false, - }, - }, + describe.each([ + [false, true, true], + [true, false, true], + [true, true, true], + [false, false, false], + ])( + 'when glAvatarForAllUserAvatars=%s and enforceGlAvatar=%s', + (glAvatarForAllUserAvatars, enforceGlAvatar, isUsingNewVersion) => { + it(`will render ${isUsingNewVersion ? 'new' : 'old'} version`, () => { + createWrapper({ enforceGlAvatar }, { glAvatarForAllUserAvatars }); + expect(wrapper.findComponent(UserAvatarLinkNew).exists()).toBe(isUsingNewVersion); + expect(wrapper.findComponent(UserAvatarLinkOld).exists()).toBe(!isUsingNewVersion); }); - }); - - it('should render `UserAvatarLinkOld` component', () => { - expect(wrapper.findComponent(UserAvatarLinkNew).exists()).toBe(false); - expect(wrapper.findComponent(UserAvatarLinkOld).exists()).toBe(true); - }); - }); + }, + ); }); diff --git a/spec/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url_spec.rb b/spec/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url_spec.rb index 564d970280c..c090c1df424 100644 --- a/spec/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url_spec.rb +++ b/spec/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url_spec.rb @@ -49,9 +49,9 @@ RSpec.describe Gitlab::BackgroundMigration::UpdateJiraTrackerDataDeploymentTypeB stub_const('CLOUD', 2) end - let!(:tracker_data_cloud) { JiraTrackerData.create!(id: 1, service_id: service_jira_cloud.id, url: "https://test-domain.atlassian.net", deployment_type: UNKNOWN) } - let!(:tracker_data_server) { JiraTrackerData.create!(id: 2, service_id: service_jira_server.id, url: "http://totally-not-jira-server.company.org", deployment_type: UNKNOWN) } - let!(:tracker_data_unknown) { JiraTrackerData.create!(id: 3, service_id: service_jira_unknown.id, url: "", deployment_type: UNKNOWN) } + let!(:tracker_data_cloud) { JiraTrackerData.create!(id: 1, integration_id: service_jira_cloud.id, url: "https://test-domain.atlassian.net", deployment_type: UNKNOWN) } + let!(:tracker_data_server) { JiraTrackerData.create!(id: 2, integration_id: service_jira_server.id, url: "http://totally-not-jira-server.company.org", deployment_type: UNKNOWN) } + let!(:tracker_data_unknown) { JiraTrackerData.create!(id: 3, integration_id: service_jira_unknown.id, url: "", deployment_type: UNKNOWN) } it "changes unknown deployment_types based on URL" do expect(JiraTrackerData.pluck(:deployment_type)).to match_array([UNKNOWN, UNKNOWN, UNKNOWN]) diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb index 26c560565e0..8ac03301322 100644 --- a/spec/lib/gitlab/ci/variables/collection_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection_spec.rb @@ -302,6 +302,7 @@ RSpec.describe Gitlab::Ci::Variables::Collection do .append(key: 'CI_BUILD_ID', value: '1') .append(key: 'RAW_VAR', value: '$TEST1', raw: true) .append(key: 'TEST1', value: 'test-3') + .append(key: 'FILEVAR1', value: 'file value 1', file: true) end context 'table tests' do @@ -311,28 +312,23 @@ RSpec.describe Gitlab::Ci::Variables::Collection do { "empty value": { value: '', - result: '', - keep_undefined: false + result: '' }, "simple expansions": { value: 'key$TEST1-$CI_BUILD_ID', - result: 'keytest-3-1', - keep_undefined: false + result: 'keytest-3-1' }, "complex expansion": { value: 'key${TEST1}-${CI_JOB_NAME}', - result: 'keytest-3-test-1', - keep_undefined: false + result: 'keytest-3-test-1' }, "complex expansions with raw variable": { value: 'key${RAW_VAR}-${CI_JOB_NAME}', - result: 'key$TEST1-test-1', - keep_undefined: false + result: 'key$TEST1-test-1' }, "missing variable not keeping original": { value: 'key${MISSING_VAR}-${CI_JOB_NAME}', - result: 'key-test-1', - keep_undefined: false + result: 'key-test-1' }, "missing variable keeping original": { value: 'key${MISSING_VAR}-${CI_JOB_NAME}', @@ -341,14 +337,24 @@ RSpec.describe Gitlab::Ci::Variables::Collection do }, "escaped characters are kept intact": { value: 'key-$TEST1-%%HOME%%-$${HOME}', - result: 'key-test-3-%%HOME%%-$${HOME}', - keep_undefined: false + result: 'key-test-3-%%HOME%%-$${HOME}' + }, + "file variable with expand_file_vars: true": { + value: 'key-$FILEVAR1-$TEST1', + result: 'key-file value 1-test-3' + }, + "file variable with expand_file_vars: false": { + value: 'key-$FILEVAR1-$TEST1', + result: 'key-$FILEVAR1-test-3', + expand_file_vars: false } } end with_them do - subject { collection.expand_value(value, keep_undefined: keep_undefined) } + let(:options) { { keep_undefined: keep_undefined, expand_file_vars: expand_file_vars }.compact } + + subject(:result) { collection.expand_value(value, **options) } it 'matches expected expansion' do is_expected.to eq(result) diff --git a/spec/migrations/20220725150127_update_jira_tracker_data_deployment_type_based_on_url_spec.rb b/spec/migrations/20220725150127_update_jira_tracker_data_deployment_type_based_on_url_spec.rb index a471b00592c..2651e46ba53 100644 --- a/spec/migrations/20220725150127_update_jira_tracker_data_deployment_type_based_on_url_spec.rb +++ b/spec/migrations/20220725150127_update_jira_tracker_data_deployment_type_based_on_url_spec.rb @@ -34,8 +34,8 @@ RSpec.describe UpdateJiraTrackerDataDeploymentTypeBasedOnUrl, :migration do # rubocop:disable Layout/LineLength # rubocop:disable RSpec/ScatteredLet - let!(:tracker_data_cloud) { JiraTrackerData.create!(id: 1, service_id: service_jira_cloud.id, url: "https://test-domain.atlassian.net", deployment_type: 0) } - let!(:tracker_data_server) { JiraTrackerData.create!(id: 2, service_id: service_jira_server.id, url: "http://totally-not-jira-server.company.org", deployment_type: 0) } + let!(:tracker_data_cloud) { JiraTrackerData.create!(id: 1, integration_id: service_jira_cloud.id, url: "https://test-domain.atlassian.net", deployment_type: 0) } + let!(:tracker_data_server) { JiraTrackerData.create!(id: 2, integration_id: service_jira_server.id, url: "http://totally-not-jira-server.company.org", deployment_type: 0) } # rubocop:enable Layout/LineLength # rubocop:enable RSpec/ScatteredLet diff --git a/spec/models/data_list_spec.rb b/spec/models/data_list_spec.rb index 67db2730a78..6e01f4786ba 100644 --- a/spec/models/data_list_spec.rb +++ b/spec/models/data_list_spec.rb @@ -8,7 +8,7 @@ RSpec.describe DataList do let(:zentao_integration) { create(:zentao_integration) } let(:cases) do [ - [jira_integration, 'Integrations::JiraTrackerData', 'service_id'], + [jira_integration, 'Integrations::JiraTrackerData', 'integration_id'], [zentao_integration, 'Integrations::ZentaoTrackerData', 'integration_id'] ] end diff --git a/spec/models/integration_spec.rb b/spec/models/integration_spec.rb index 66e2eaee181..647b1d5de7f 100644 --- a/spec/models/integration_spec.rb +++ b/spec/models/integration_spec.rb @@ -12,7 +12,7 @@ RSpec.describe Integration do it { is_expected.to belong_to(:project).inverse_of(:integrations) } it { is_expected.to belong_to(:group).inverse_of(:integrations) } it { is_expected.to have_one(:issue_tracker_data).autosave(true).inverse_of(:integration).with_foreign_key(:service_id).class_name('Integrations::IssueTrackerData') } - it { is_expected.to have_one(:jira_tracker_data).autosave(true).inverse_of(:integration).with_foreign_key(:service_id).class_name('Integrations::JiraTrackerData') } + it { is_expected.to have_one(:jira_tracker_data).autosave(true).inverse_of(:integration).with_foreign_key(:integration_id).class_name('Integrations::JiraTrackerData') } end describe 'validations' do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 2525b167d08..afb321c0777 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -873,6 +873,12 @@ RSpec.describe Project, factory_default: :keep do end end + describe '#ci_allow_fork_pipelines_to_run_in_parent_project?' do + it_behaves_like 'a ci_cd_settings predicate method', prefix: 'ci_' do + let(:delegated_method) { :allow_fork_pipelines_to_run_in_parent_project? } + end + end + describe '#ci_job_token_scope_enabled?' do it_behaves_like 'a ci_cd_settings predicate method', prefix: 'ci_' do let(:delegated_method) { :job_token_scope_enabled? } diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb index ace65307321..fe2d8f0f670 100644 --- a/spec/presenters/ci/build_runner_presenter_spec.rb +++ b/spec/presenters/ci/build_runner_presenter_spec.rb @@ -309,25 +309,64 @@ RSpec.describe Ci::BuildRunnerPresenter do end describe '#runner_variables' do - subject { presenter.runner_variables } + subject(:runner_variables) { presenter.runner_variables } let_it_be(:project) { create(:project, :repository) } - shared_examples 'returns an array with the expected variables' do - it 'returns an array' do - is_expected.to be_an_instance_of(Array) + let(:sha) { project.repository.commit.sha } + let(:pipeline) { create(:ci_pipeline, sha: sha, project: project) } + let(:build) { create(:ci_build, pipeline: pipeline) } + + it 'returns an array' do + is_expected.to be_an_instance_of(Array) + end + + it 'returns the expected variables' do + is_expected.to eq(presenter.variables.to_runner_variables) + end + + context 'when there are variables to expand' do + before_all do + create(:ci_variable, project: project, + key: 'regular_var', + value: 'value 1') + create(:ci_variable, project: project, + key: 'file_var', + value: 'value 2', + variable_type: :file) + create(:ci_variable, project: project, + key: 'var_with_variables', + value: 'value 3 and $regular_var and $file_var and $undefined_var') end - it 'returns the expected variables' do - is_expected.to eq(presenter.variables.to_runner_variables) + it 'returns variables with expanded' do + expect(runner_variables).to include( + { key: 'regular_var', value: 'value 1', + public: false, masked: false }, + { key: 'file_var', value: 'value 2', + public: false, masked: false, file: true }, + { key: 'var_with_variables', value: 'value 3 and value 1 and $file_var and $undefined_var', + public: false, masked: false } + ) end - end - let(:sha) { project.repository.commit.sha } - let(:pipeline) { create(:ci_pipeline, sha: sha, project: project) } - let(:build) { create(:ci_build, pipeline: pipeline) } + context 'when the FF ci_stop_expanding_file_vars_for_runners is disabled' do + before do + stub_feature_flags(ci_stop_expanding_file_vars_for_runners: false) + end - it_behaves_like 'returns an array with the expected variables' + it 'returns variables with expanded' do + expect(runner_variables).to include( + { key: 'regular_var', value: 'value 1', + public: false, masked: false }, + { key: 'file_var', value: 'value 2', + public: false, masked: false, file: true }, + { key: 'var_with_variables', value: 'value 3 and value 1 and value 2 and $undefined_var', + public: false, masked: false } + ) + end + end + end end describe '#runner_variables subset' do diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index ec6d7d4bfb4..9df9c75b020 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -231,6 +231,33 @@ RSpec.describe API::Members do end end end + + context 'with ancestral membership' do + shared_examples 'response with correct access levels' do + it do + get api("/#{source_type.pluralize}/#{source.id}/members/#{all ? 'all/' : ''}#{developer.id}", developer) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['access_level']).to eq(Member::MAINTAINER) + end + end + + before do + source.add_maintainer(developer) + end + + include_examples 'response with correct access levels' + + context 'having email invite' do + before do + Member + .find_by(source: group, user: developer) + .update!(invite_email: 'email@email.com') + end + + include_examples 'response with correct access levels' + end + end end end diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml index af93c1b6de2..670035187cb 100644 --- a/spec/requests/api/project_attributes.yml +++ b/spec/requests/api/project_attributes.yml @@ -101,6 +101,7 @@ ci_cd_settings: job_token_scope_enabled: ci_job_token_scope_enabled separated_caches: ci_separated_caches opt_in_jwt: ci_opt_in_jwt + allow_fork_pipelines_to_run_in_parent_project: ci_allow_fork_pipelines_to_run_in_parent_project build_import_state: # import_state unexposed_attributes: diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index d3c65e11ed7..94688833d88 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -2427,6 +2427,7 @@ RSpec.describe API::Projects do expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved) expect(json_response['ci_default_git_depth']).to eq(project.ci_default_git_depth) expect(json_response['ci_forward_deployment_enabled']).to eq(project.ci_forward_deployment_enabled) + expect(json_response['ci_allow_fork_pipelines_to_run_in_parent_project']).to eq(project.ci_allow_fork_pipelines_to_run_in_parent_project) expect(json_response['ci_separated_caches']).to eq(project.ci_separated_caches) expect(json_response['merge_method']).to eq(project.merge_method.to_s) expect(json_response['squash_option']).to eq(project.squash_option.to_s) @@ -3692,6 +3693,7 @@ RSpec.describe API::Projects do merge_method: 'ff', ci_default_git_depth: 20, ci_forward_deployment_enabled: false, + ci_allow_fork_pipelines_to_run_in_parent_project: false, ci_separated_caches: false, description: 'new description' } diff --git a/spec/services/bulk_update_integration_service_spec.rb b/spec/services/bulk_update_integration_service_spec.rb index e3e38aacaa2..7c5bd1db565 100644 --- a/spec/services/bulk_update_integration_service_spec.rb +++ b/spec/services/bulk_update_integration_service_spec.rb @@ -71,7 +71,7 @@ RSpec.describe BulkUpdateIntegrationService do context 'with integration with data fields' do let(:excluded_attributes) do - %w[id service_id created_at updated_at encrypted_properties encrypted_properties_iv] + %w[id integration_id created_at updated_at encrypted_properties encrypted_properties_iv] end it 'updates the data fields from the integration', :aggregate_failures do diff --git a/spec/services/merge_requests/create_pipeline_service_spec.rb b/spec/services/merge_requests/create_pipeline_service_spec.rb index 03a37ea59a3..c443d758a77 100644 --- a/spec/services/merge_requests/create_pipeline_service_spec.rb +++ b/spec/services/merge_requests/create_pipeline_service_spec.rb @@ -74,6 +74,16 @@ RSpec.describe MergeRequests::CreatePipelineService do expect(response.payload.project).to eq(project) end + context 'when the feature is disabled in CI/CD settings' do + before do + project.update!(ci_allow_fork_pipelines_to_run_in_parent_project: false) + end + + it 'creates a pipeline in the source project' do + expect(response.payload.project).to eq(source_project) + end + end + context 'when source branch is protected' do context 'when actor does not have permission to update the protected branch in target project' do let!(:protected_branch) { create(:protected_branch, name: '*', project: project) } |