diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-10 12:18:48 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-10 12:18:48 +0000 |
commit | 190128fc72e015c383e7a96c128276d1833f3beb (patch) | |
tree | c9defcce34e8e0661c3a2cebe78847e9d9f14a39 /spec | |
parent | de74d20b2596c8d27987744d24a7fc09fbe8ff37 (diff) | |
download | gitlab-ce-190128fc72e015c383e7a96c128276d1833f3beb.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
34 files changed, 418 insertions, 109 deletions
diff --git a/spec/events/projects/project_deleted_event_spec.rb b/spec/events/projects/project_deleted_event_spec.rb new file mode 100644 index 00000000000..fd8cec7271b --- /dev/null +++ b/spec/events/projects/project_deleted_event_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::ProjectDeletedEvent do + where(:data, :valid) do + [ + [{ project_id: 1, namespace_id: 2 }, true], + [{ project_id: 1 }, false], + [{ namespace_id: 1 }, false], + [{ project_id: 'foo', namespace_id: 2 }, false], + [{ project_id: 1, namespace_id: 'foo' }, false], + [{ project_id: [], namespace_id: 2 }, false], + [{ project_id: 1, namespace_id: [] }, false], + [{ project_id: {}, namespace_id: 2 }, false], + [{ project_id: 1, namespace_id: {} }, false], + ['foo', false], + [123, false], + [[], false] + ] + end + + with_them do + it 'validates data' do + constructor = -> { described_class.new(data: data) } + + if valid + expect { constructor.call }.not_to raise_error + else + expect { constructor.call }.to raise_error(Gitlab::EventStore::InvalidEvent) + end + end + end +end diff --git a/spec/features/admin/admin_mode/logout_spec.rb b/spec/features/admin/admin_mode/logout_spec.rb index 58bea5c4b5f..f2f6e26fbee 100644 --- a/spec/features/admin/admin_mode/logout_spec.rb +++ b/spec/features/admin/admin_mode/logout_spec.rb @@ -32,7 +32,7 @@ RSpec.describe 'Admin Mode Logout', :js do it 'disable shows flash notice' do gitlab_disable_admin_mode - expect(page).to have_selector('.flash-notice') + expect(page).to have_selector('[data-testid="alert-info"]') end context 'on a read-only instance' do diff --git a/spec/features/groups/members/leave_group_spec.rb b/spec/features/groups/members/leave_group_spec.rb index e6bf1ffc2f7..9612c6625f6 100644 --- a/spec/features/groups/members/leave_group_spec.rb +++ b/spec/features/groups/members/leave_group_spec.rb @@ -79,7 +79,7 @@ RSpec.describe 'Groups > Members > Leave group' do visit group_path(group, leave: 1) - expect(find('.flash-alert')).to have_content 'You do not have permission to leave this group' + expect(find('[data-testid="alert-danger"]')).to have_content 'You do not have permission to leave this group' end def left_group_message(group) diff --git a/spec/features/issues/filtered_search/recent_searches_spec.rb b/spec/features/issues/filtered_search/recent_searches_spec.rb index 3ddcbf1bd01..3929d3694ff 100644 --- a/spec/features/issues/filtered_search/recent_searches_spec.rb +++ b/spec/features/issues/filtered_search/recent_searches_spec.rb @@ -104,7 +104,7 @@ RSpec.describe 'Recent searches', :js do set_recent_searches(project_1_local_storage_key, 'fail') visit project_issues_path(project_1) - expect(find('.flash-alert')).to have_text('An error occurred while parsing recent searches') + expect(find('[data-testid="alert-danger"]')).to have_text('An error occurred while parsing recent searches') end context 'on tablet/mobile screen' do diff --git a/spec/features/profiles/password_spec.rb b/spec/features/profiles/password_spec.rb index 25fe43617fd..898e2c2aa59 100644 --- a/spec/features/profiles/password_spec.rb +++ b/spec/features/profiles/password_spec.rb @@ -41,7 +41,7 @@ RSpec.describe 'Profile > Password' do it 'shows a success message' do fill_passwords(Gitlab::Password.test_default, Gitlab::Password.test_default) - page.within('.flash-notice') do + page.within('[data-testid="alert-info"]') do expect(page).to have_content('Password was successfully updated. Please sign in again.') end end diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index c8cbf396098..77194fd6ca1 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -984,7 +984,7 @@ RSpec.describe 'File blob', :js do visit_blob('README.md') expect(page).to have_selector('.file-content') - expect(page).not_to have_selector('.flash-alert') + expect(page).not_to have_selector('[data-testid="alert-danger"]') end it 'displays a GPG badge' do diff --git a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb index c8a9f959188..c9fee9bee7a 100644 --- a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb +++ b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb @@ -21,6 +21,6 @@ RSpec.describe 'Projects > Members > Group member cannot leave group project' do it 'renders a flash message if attempting to leave by url', :js do visit project_path(project, leave: 1) - expect(find('.flash-alert')).to have_content 'You do not have permission to leave this project' + expect(find('[data-testid="alert-danger"]')).to have_content 'You do not have permission to leave this project' end end diff --git a/spec/features/projects/network_graph_spec.rb b/spec/features/projects/network_graph_spec.rb index 4ae809399b6..1ee0ea51e53 100644 --- a/spec/features/projects/network_graph_spec.rb +++ b/spec/features/projects/network_graph_spec.rb @@ -96,7 +96,7 @@ RSpec.describe 'Project Network Graph', :js do find('button').click end - expect(page).to have_selector '.flash-alert', text: "Git revision ';' does not exist." + expect(page).to have_selector '[data-testid="alert-danger"]', text: "Git revision ';' does not exist." end end diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index e941ad7643e..dde8bd48790 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -857,7 +857,7 @@ RSpec.describe 'Pipelines', :js do it 'increments jobs_cache_index' do click_button 'Clear runner caches' wait_for_requests - expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.' + expect(page.find('[data-testid="alert-info"]')).to have_content 'Project cache successfully reset.' end end @@ -865,7 +865,7 @@ RSpec.describe 'Pipelines', :js do it 'sets jobs_cache_index to 1' do click_button 'Clear runner caches' wait_for_requests - expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.' + expect(page.find('[data-testid="alert-info"]')).to have_content 'Project cache successfully reset.' end end end diff --git a/spec/features/projects/tree/tree_show_spec.rb b/spec/features/projects/tree/tree_show_spec.rb index f8bbaa9535b..cd94e6da018 100644 --- a/spec/features/projects/tree/tree_show_spec.rb +++ b/spec/features/projects/tree/tree_show_spec.rb @@ -25,7 +25,7 @@ RSpec.describe 'Projects tree', :js do expect(page).to have_selector('.tree-item') expect(page).to have_content('add tests for .gitattributes custom highlighting') - expect(page).not_to have_selector('.flash-alert') + expect(page).not_to have_selector('[data-testid="alert-danger"]') expect(page).not_to have_selector('[data-qa-selector="label-lfs"]', text: 'LFS') # rubocop:disable QA/SelectorUsage end @@ -36,7 +36,7 @@ RSpec.describe 'Projects tree', :js do expect(page).to have_selector('.tree-item') expect(page).to have_content('add spaces in whitespace file') expect(page).not_to have_selector('[data-qa-selector="label-lfs"]', text: 'LFS') # rubocop:disable QA/SelectorUsage - expect(page).not_to have_selector('.flash-alert') + expect(page).not_to have_selector('[data-testid="alert-danger"]') end it 'renders tree table with non-ASCII filenames without errors' do @@ -46,7 +46,7 @@ RSpec.describe 'Projects tree', :js do expect(page).to have_selector('.tree-item') expect(page).to have_content('Files, encoding and much more') expect(page).to have_content('テスト.txt') - expect(page).not_to have_selector('.flash-alert') + expect(page).not_to have_selector('[data-testid="alert-danger"]') end context "with a tree that contains pathspec characters" do @@ -139,7 +139,7 @@ RSpec.describe 'Projects tree', :js do wait_for_requests expect(page).to have_selector('.tree-item') - expect(page).not_to have_selector('.flash-alert') + expect(page).not_to have_selector('[data-testid="alert-danger"]') end context 'for signed commit' do diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 31f30075327..1049f8bc18f 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -236,7 +236,7 @@ RSpec.describe 'Project' do it 'does not show an error' do wait_for_requests - expect(page).not_to have_selector('.flash-alert') + expect(page).not_to have_selector('[data-testid="alert-danger"]') end end @@ -316,7 +316,7 @@ RSpec.describe 'Project' do wait_for_requests expect(page).to have_selector('.tree-item') - expect(page).not_to have_selector('.flash-alert') + expect(page).not_to have_selector('[data-testid="alert-danger"]') end context 'for signed commit' do diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb index 2ddd86dd807..1f1824c897e 100644 --- a/spec/features/triggers_spec.rb +++ b/spec/features/triggers_spec.rb @@ -34,7 +34,7 @@ RSpec.describe 'Triggers', :js do click_button 'Add trigger' aggregate_failures 'display creation notice and trigger is created' do - expect(page.find('.flash-notice')).to have_content 'Trigger was created successfully.' + expect(page.find('[data-testid="alert-info"]')).to have_content 'Trigger was created successfully.' expect(page.find('.triggers-list')).to have_content 'trigger desc' expect(page.find('.triggers-list .trigger-owner')).to have_content user.name end @@ -63,7 +63,7 @@ RSpec.describe 'Triggers', :js do click_button 'Save trigger' aggregate_failures 'display update notice and trigger is updated' do - expect(page.find('.flash-notice')).to have_content 'Trigger was successfully updated.' + expect(page.find('[data-testid="alert-info"]')).to have_content 'Trigger was successfully updated.' expect(page.find('.triggers-list')).to have_content new_trigger_title expect(page.find('.triggers-list .trigger-owner')).to have_content user.name end @@ -89,7 +89,7 @@ RSpec.describe 'Triggers', :js do end aggregate_failures 'trigger is removed' do - expect(page.find('.flash-notice')).to have_content 'Trigger removed' + expect(page.find('[data-testid="alert-info"]')).to have_content 'Trigger removed' expect(page).to have_css('[data-testid="no_triggers_content"]') end end diff --git a/spec/features/users/logout_spec.rb b/spec/features/users/logout_spec.rb index ffb8785b277..3129eb5e6f3 100644 --- a/spec/features/users/logout_spec.rb +++ b/spec/features/users/logout_spec.rb @@ -19,7 +19,7 @@ RSpec.describe 'Logout/Sign out', :js do it 'sign out does not show signed out flash notice' do gitlab_sign_out - expect(page).not_to have_selector('.flash-notice') + expect(page).not_to have_selector('[data-testid="alert-info"]') end context 'on a read-only instance' do diff --git a/spec/frontend/error_tracking/components/error_details_spec.js b/spec/frontend/error_tracking/components/error_details_spec.js index 493a96c207b..03ae437a89e 100644 --- a/spec/frontend/error_tracking/components/error_details_spec.js +++ b/spec/frontend/error_tracking/components/error_details_spec.js @@ -583,33 +583,25 @@ describe('ErrorDetails', () => { expect(Tracking.event).toHaveBeenCalledWith(category, action); }); - it('should track IGNORE status update', () => { + it('should track IGNORE status update', async () => { Tracking.event.mockClear(); - findUpdateIgnoreStatusButton().vm.$emit('click'); - setImmediate(() => { - const { category, action } = trackErrorStatusUpdateOptions('ignored'); - expect(Tracking.event).toHaveBeenCalledWith(category, action); - }); + await findUpdateIgnoreStatusButton().trigger('click'); + const { category, action } = trackErrorStatusUpdateOptions('ignored'); + expect(Tracking.event).toHaveBeenCalledWith(category, action); }); - it('should track RESOLVE status update', () => { + it('should track RESOLVE status update', async () => { Tracking.event.mockClear(); - findUpdateResolveStatusButton().vm.$emit('click'); - setImmediate(() => { - const { category, action } = trackErrorStatusUpdateOptions('resolved'); - expect(Tracking.event).toHaveBeenCalledWith(category, action); - }); + await findUpdateResolveStatusButton().trigger('click'); + const { category, action } = trackErrorStatusUpdateOptions('resolved'); + expect(Tracking.event).toHaveBeenCalledWith(category, action); }); - it('should track external Sentry link views', () => { + it('should track external Sentry link views', async () => { Tracking.event.mockClear(); - findExternalUrl().trigger('click'); - setImmediate(() => { - const { category, action, label, property } = trackClickErrorLinkToSentryOptions( - externalUrl, - ); - expect(Tracking.event).toHaveBeenCalledWith(category, action, { label, property }); - }); + await findExternalUrl().trigger('click'); + const { category, action, label, property } = trackClickErrorLinkToSentryOptions(externalUrl); + expect(Tracking.event).toHaveBeenCalledWith(category, action, { label, property }); }); }); }); diff --git a/spec/frontend/error_tracking/components/error_tracking_list_spec.js b/spec/frontend/error_tracking/components/error_tracking_list_spec.js index d4d145b5840..59671c175e7 100644 --- a/spec/frontend/error_tracking/components/error_tracking_list_spec.js +++ b/spec/frontend/error_tracking/components/error_tracking_list_spec.js @@ -447,7 +447,7 @@ describe('ErrorTrackingList', () => { expect(Tracking.event).toHaveBeenCalledWith(category, action); }); - it('should track status updates', () => { + it('should track status updates', async () => { Tracking.event.mockClear(); const status = 'ignored'; findErrorActions().vm.$emit('update-issue-status', { @@ -455,10 +455,10 @@ describe('ErrorTrackingList', () => { status, }); - setImmediate(() => { - const { category, action } = trackErrorStatusUpdateOptions(status); - expect(Tracking.event).toHaveBeenCalledWith(category, action); - }); + await nextTick(); + + const { category, action } = trackErrorStatusUpdateOptions(status); + expect(Tracking.event).toHaveBeenCalledWith(category, action); }); }); }); diff --git a/spec/frontend/feature_flags/components/feature_flags_spec.js b/spec/frontend/feature_flags/components/feature_flags_spec.js index 728c6abb23a..d27b23c5cd1 100644 --- a/spec/frontend/feature_flags/components/feature_flags_spec.js +++ b/spec/frontend/feature_flags/components/feature_flags_spec.js @@ -71,12 +71,12 @@ describe('Feature flags', () => { describe('when limit exceeded', () => { const provideData = { ...mockData, featureFlagsLimitExceeded: true }; - beforeEach((done) => { + beforeEach(() => { mock .onGet(`${TEST_HOST}/endpoint.json`, { params: { page: '1' } }) .reply(200, getRequestData, {}); factory(provideData); - setImmediate(done); + return waitForPromises(); }); it('makes the new feature flag button do nothing if clicked', () => { @@ -116,12 +116,12 @@ describe('Feature flags', () => { userListPath: null, }; - beforeEach((done) => { + beforeEach(() => { mock .onGet(`${TEST_HOST}/endpoint.json`, { params: { page: '1' } }) .reply(200, getRequestData, {}); factory(provideData); - setImmediate(done); + return waitForPromises(); }); it('does not render configure button', () => { @@ -202,7 +202,7 @@ describe('Feature flags', () => { }); describe('with paginated feature flags', () => { - beforeEach((done) => { + beforeEach(() => { mock.onGet(mockState.endpoint, { params: { page: '1' } }).replyOnce(200, getRequestData, { 'x-next-page': '2', 'x-page': '1', @@ -214,7 +214,7 @@ describe('Feature flags', () => { factory(); jest.spyOn(store, 'dispatch'); - setImmediate(done); + return waitForPromises(); }); it('should render a table with feature flags', () => { @@ -270,11 +270,11 @@ describe('Feature flags', () => { }); describe('unsuccessful request', () => { - beforeEach((done) => { + beforeEach(() => { mock.onGet(mockState.endpoint, { params: { page: '1' } }).replyOnce(500, {}); factory(); - setImmediate(done); + return waitForPromises(); }); it('should render error state', () => { @@ -300,12 +300,12 @@ describe('Feature flags', () => { }); describe('rotate instance id', () => { - beforeEach((done) => { + beforeEach(() => { mock .onGet(`${TEST_HOST}/endpoint.json`, { params: { page: '1' } }) .reply(200, getRequestData, {}); factory(); - setImmediate(done); + return waitForPromises(); }); it('should fire the rotate action when a `token` event is received', () => { diff --git a/spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js b/spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js index 44f67f269a2..c4e125e96da 100644 --- a/spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js +++ b/spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js @@ -1,6 +1,7 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import FilteredSearchSpecHelper from 'helpers/filtered_search_spec_helper'; +import waitForPromises from 'helpers/wait_for_promises'; import FilteredSearchVisualTokens from '~/filtered_search/filtered_search_visual_tokens'; describe('Filtered Search Visual Tokens', () => { @@ -715,18 +716,16 @@ describe('Filtered Search Visual Tokens', () => { `); }); - it('renders a author token value element', () => { + it('renders a author token value element', async () => { const { tokenNameElement, tokenValueElement } = findElements(authorToken); const tokenName = tokenNameElement.textContent; const tokenValue = 'new value'; subject.renderVisualTokenValue(authorToken, tokenName, tokenValue); - jest.runOnlyPendingTimers(); + await waitForPromises(); - setImmediate(() => { - expect(tokenValueElement.textContent).toBe(tokenValue); - }); + expect(tokenValueElement.textContent).toBe(tokenValue); }); }); }); diff --git a/spec/frontend/flash_spec.js b/spec/frontend/flash_spec.js index d5451ec2064..942e2c330fa 100644 --- a/spec/frontend/flash_spec.js +++ b/spec/frontend/flash_spec.js @@ -517,16 +517,12 @@ describe('Flash', () => { `; }); - it('removes global flash on click', (done) => { + it('removes global flash on click', () => { addDismissFlashClickListener(el, false); el.querySelector('.js-close-icon').click(); - setImmediate(() => { - expect(document.querySelector('.flash')).toBeNull(); - - done(); - }); + expect(document.querySelector('.flash')).toBeNull(); }); }); diff --git a/spec/frontend/gl_form_spec.js b/spec/frontend/gl_form_spec.js index 07487fbb60e..ab5627ce216 100644 --- a/spec/frontend/gl_form_spec.js +++ b/spec/frontend/gl_form_spec.js @@ -8,7 +8,7 @@ describe('GLForm', () => { const testContext = {}; describe('when instantiated', () => { - beforeEach((done) => { + beforeEach(() => { window.gl = window.gl || {}; testContext.form = $('<form class="gfm-form"><textarea class="js-gfm-input"></form>'); @@ -18,22 +18,11 @@ describe('GLForm', () => { jest.spyOn($.prototype, 'css').mockImplementation(() => {}); testContext.glForm = new GLForm(testContext.form, false); - - setImmediate(() => { - $.prototype.off.mockClear(); - $.prototype.on.mockClear(); - $.prototype.css.mockClear(); - done(); - }); }); describe('setupAutosize', () => { - beforeEach((done) => { + beforeEach(() => { testContext.glForm.setupAutosize(); - - setImmediate(() => { - done(); - }); }); it('should register an autosize event handler on the textarea', () => { diff --git a/spec/frontend/ide/components/commit_sidebar/list_item_spec.js b/spec/frontend/ide/components/commit_sidebar/list_item_spec.js index 78810da7f2c..dea920ecb5e 100644 --- a/spec/frontend/ide/components/commit_sidebar/list_item_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/list_item_spec.js @@ -1,5 +1,6 @@ import Vue, { nextTick } from 'vue'; import { trimText } from 'helpers/text_helper'; +import waitForPromises from 'helpers/wait_for_promises'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import listItem from '~/ide/components/commit_sidebar/list_item.vue'; import { createRouter } from '~/ide/ide_router'; @@ -55,32 +56,28 @@ describe('Multi-file editor commit sidebar list item', () => { expect(findPathText()).toEqual(f.name); }); - it('opens a closed file in the editor when clicking the file path', (done) => { + it('opens a closed file in the editor when clicking the file path', async () => { jest.spyOn(vm, 'openPendingTab'); jest.spyOn(router, 'push').mockImplementation(() => {}); findPathEl.click(); - setImmediate(() => { - expect(vm.openPendingTab).toHaveBeenCalled(); - expect(router.push).toHaveBeenCalled(); + await nextTick(); - done(); - }); + expect(vm.openPendingTab).toHaveBeenCalled(); + expect(router.push).toHaveBeenCalled(); }); - it('calls updateViewer with diff when clicking file', (done) => { + it('calls updateViewer with diff when clicking file', async () => { jest.spyOn(vm, 'openFileInEditor'); jest.spyOn(vm, 'updateViewer'); jest.spyOn(router, 'push').mockImplementation(() => {}); findPathEl.click(); - setImmediate(() => { - expect(vm.updateViewer).toHaveBeenCalledWith('diff'); + await waitForPromises(); - done(); - }); + expect(vm.updateViewer).toHaveBeenCalledWith('diff'); }); describe('computed', () => { diff --git a/spec/frontend/pipeline_wizard/components/editor_spec.js b/spec/frontend/pipeline_wizard/components/editor_spec.js new file mode 100644 index 00000000000..446412a4f02 --- /dev/null +++ b/spec/frontend/pipeline_wizard/components/editor_spec.js @@ -0,0 +1,69 @@ +import { mount } from '@vue/test-utils'; +import { Document } from 'yaml'; +import YamlEditor from '~/pipeline_wizard/components/editor.vue'; + +describe('Pages Yaml Editor wrapper', () => { + const defaultOptions = { + propsData: { doc: new Document({ foo: 'bar' }), filename: 'foo.yml' }, + }; + + describe('mount hook', () => { + const wrapper = mount(YamlEditor, defaultOptions); + + it('editor is mounted', () => { + expect(wrapper.vm.editor).not.toBeFalsy(); + expect(wrapper.find('.gl-source-editor').exists()).toBe(true); + }); + }); + + describe('watchers', () => { + describe('doc', () => { + const doc = new Document({ baz: ['bar'] }); + let wrapper; + + beforeEach(() => { + wrapper = mount(YamlEditor, defaultOptions); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it("causes the editor's value to be set to the stringified document", async () => { + await wrapper.setProps({ doc }); + expect(wrapper.vm.editor.getValue()).toEqual(doc.toString()); + }); + + it('emits an update:yaml event with the yaml representation of doc', async () => { + await wrapper.setProps({ doc }); + const changeEvents = wrapper.emitted('update:yaml'); + expect(changeEvents[2]).toEqual([doc.toString()]); + }); + + it('does not cause the touch event to be emitted', () => { + wrapper.setProps({ doc }); + expect(wrapper.emitted('touch')).not.toBeTruthy(); + }); + }); + + describe('highlight', () => { + const highlight = 'foo'; + const wrapper = mount(YamlEditor, defaultOptions); + + it('calls editor.highlight(path, keep=true)', async () => { + const highlightSpy = jest.spyOn(wrapper.vm.yamlEditorExtension.obj, 'highlight'); + await wrapper.setProps({ highlight }); + expect(highlightSpy).toHaveBeenCalledWith(expect.anything(), highlight, true); + }); + }); + }); + + describe('events', () => { + const wrapper = mount(YamlEditor, defaultOptions); + + it('emits touch if content is changed in editor', async () => { + await wrapper.vm.editor.setValue('foo: boo'); + expect(wrapper.emitted('touch')).toBeTruthy(); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js b/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js index 1ff98d4aec5..663ebd3e12f 100644 --- a/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js +++ b/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js @@ -2,16 +2,20 @@ import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; import { HIGHLIGHT_CLASS_NAME } from '~/vue_shared/components/blob_viewers/constants'; import SimpleViewer from '~/vue_shared/components/blob_viewers/simple_viewer.vue'; +import LineHighlighter from '~/blob/line_highlighter'; + +jest.mock('~/blob/line_highlighter'); describe('Blob Simple Viewer component', () => { let wrapper; const contentMock = `<span id="LC1">First</span>\n<span id="LC2">Second</span>\n<span id="LC3">Third</span>`; const blobHash = 'foo-bar'; - function createComponent(content = contentMock, isRawContent = false) { + function createComponent(content = contentMock, isRawContent = false, glFeatures = {}) { wrapper = shallowMount(SimpleViewer, { provide: { blobHash, + glFeatures, }, propsData: { content, @@ -26,6 +30,20 @@ describe('Blob Simple Viewer component', () => { wrapper.destroy(); }); + describe('refactorBlobViewer feature flag', () => { + it('loads the LineHighlighter if refactorBlobViewer is enabled', () => { + createComponent('', false, { refactorBlobViewer: true }); + + expect(LineHighlighter).toHaveBeenCalled(); + }); + + it('does not load the LineHighlighter if refactorBlobViewer is disabled', () => { + createComponent('', false, { refactorBlobViewer: false }); + + expect(LineHighlighter).not.toHaveBeenCalled(); + }); + }); + it('does not fail if content is empty', () => { const spy = jest.spyOn(window.console, 'error'); createComponent(''); diff --git a/spec/frontend/vue_shared/components/confirm_fork_modal_spec.js b/spec/frontend/vue_shared/components/confirm_fork_modal_spec.js new file mode 100644 index 00000000000..1cde92cf522 --- /dev/null +++ b/spec/frontend/vue_shared/components/confirm_fork_modal_spec.js @@ -0,0 +1,80 @@ +import { GlModal } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ConfirmForkModal, { i18n } from '~/vue_shared/components/confirm_fork_modal.vue'; + +describe('vue_shared/components/confirm_fork_modal', () => { + let wrapper = null; + + const forkPath = '/fake/fork/path'; + const modalId = 'confirm-fork-modal'; + const defaultProps = { modalId, forkPath }; + + const findModal = () => wrapper.findComponent(GlModal); + const findModalProp = (prop) => findModal().props(prop); + const findModalActionProps = () => findModalProp('actionPrimary'); + + const createComponent = (props = {}) => + shallowMountExtended(ConfirmForkModal, { + propsData: { + ...defaultProps, + ...props, + }, + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('visible = false', () => { + beforeEach(() => { + wrapper = createComponent(); + }); + + it('sets the visible prop to `false`', () => { + expect(findModalProp('visible')).toBe(false); + }); + + it('sets the modal title', () => { + const title = findModalProp('title'); + expect(title).toBe(i18n.title); + }); + + it('sets the modal id', () => { + const fakeModalId = findModalProp('modalId'); + expect(fakeModalId).toBe(modalId); + }); + + it('has the fork path button', () => { + const modalProps = findModalActionProps(); + expect(modalProps.text).toBe(i18n.btnText); + expect(modalProps.attributes.variant).toBe('confirm'); + }); + + it('sets the correct fork path', () => { + const modalProps = findModalActionProps(); + expect(modalProps.attributes.href).toBe(forkPath); + }); + + it('has the fork message', () => { + expect(findModal().text()).toContain(i18n.message); + }); + }); + + describe('visible = true', () => { + beforeEach(() => { + wrapper = createComponent({ visible: true }); + }); + + it('sets the visible prop to `true`', () => { + expect(findModalProp('visible')).toBe(true); + }); + + it('emits the `change` event if the modal is hidden', () => { + expect(wrapper.emitted('change')).toBeUndefined(); + + findModal().vm.$emit('change', false); + + expect(wrapper.emitted('change')).toEqual([[false]]); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/web_ide_link_spec.js b/spec/frontend/vue_shared/components/web_ide_link_spec.js index 9b7c594b910..5589cbfd08f 100644 --- a/spec/frontend/vue_shared/components/web_ide_link_spec.js +++ b/spec/frontend/vue_shared/components/web_ide_link_spec.js @@ -4,6 +4,7 @@ import { nextTick } from 'vue'; import ActionsButton from '~/vue_shared/components/actions_button.vue'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import WebIdeLink from '~/vue_shared/components/web_ide_link.vue'; +import ConfirmForkModal from '~/vue_shared/components/confirm_fork_modal.vue'; import { stubComponent } from 'helpers/stub_component'; import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper'; @@ -13,6 +14,7 @@ const TEST_WEB_IDE_URL = '/-/ide/project/gitlab-test/test/edit/main/-/'; const TEST_GITPOD_URL = 'https://gitpod.test/'; const TEST_USER_PREFERENCES_GITPOD_PATH = '/-/profile/preferences#user_gitpod_enabled'; const TEST_USER_PROFILE_ENABLE_GITPOD_PATH = '/-/profile?user%5Bgitpod_enabled%5D=true'; +const forkPath = '/some/fork/path'; const ACTION_EDIT = { href: TEST_EDIT_URL, @@ -74,6 +76,7 @@ describe('Web IDE link component', () => { editUrl: TEST_EDIT_URL, webIdeUrl: TEST_WEB_IDE_URL, gitpodUrl: TEST_GITPOD_URL, + forkPath, ...props, }, stubs: { @@ -96,6 +99,7 @@ describe('Web IDE link component', () => { const findActionsButton = () => wrapper.find(ActionsButton); const findLocalStorageSync = () => wrapper.find(LocalStorageSync); const findModal = () => wrapper.findComponent(GlModal); + const findForkConfirmModal = () => wrapper.findComponent(ConfirmForkModal); it.each([ { @@ -231,16 +235,28 @@ describe('Web IDE link component', () => { }); describe('edit actions', () => { - it.each([ + const testActions = [ { - props: { showWebIdeButton: true, showEditButton: false }, + props: { + showWebIdeButton: true, + showEditButton: false, + forkPath, + forkModalId: 'edit-modal', + }, expectedEventPayload: 'ide', }, { - props: { showWebIdeButton: false, showEditButton: true }, + props: { + showWebIdeButton: false, + showEditButton: true, + forkPath, + forkModalId: 'webide-modal', + }, expectedEventPayload: 'simple', }, - ])( + ]; + + it.each(testActions)( 'emits the correct event when an action handler is called', async ({ props, expectedEventPayload }) => { createComponent({ ...props, needsToFork: true, disableForkModal: true }); @@ -250,6 +266,29 @@ describe('Web IDE link component', () => { expect(wrapper.emitted('edit')).toEqual([[expectedEventPayload]]); }, ); + + it.each(testActions)('renders the fork confirmation modal', async ({ props }) => { + createComponent({ ...props, needsToFork: true }); + + expect(findForkConfirmModal().exists()).toBe(true); + expect(findForkConfirmModal().props()).toEqual({ + visible: false, + forkPath, + modalId: props.forkModalId, + }); + }); + + it.each(testActions)('opens the modal when the button is clicked', async ({ props }) => { + createComponent({ ...props, needsToFork: true }, mountExtended); + + await findActionsButton().trigger('click'); + + expect(findForkConfirmModal().props()).toEqual({ + visible: true, + forkPath, + modalId: props.forkModalId, + }); + }); }); describe('when Gitpod is not enabled', () => { diff --git a/spec/lib/gitlab/changelog/release_spec.rb b/spec/lib/gitlab/changelog/release_spec.rb index d8434821640..defcec5aa65 100644 --- a/spec/lib/gitlab/changelog/release_spec.rb +++ b/spec/lib/gitlab/changelog/release_spec.rb @@ -139,6 +139,16 @@ RSpec.describe Gitlab::Changelog::Release do OUT end end + + context 'when template parser raises an error' do + before do + allow(config).to receive(:template).and_raise(Gitlab::TemplateParser::Error) + end + + it 'raises a Changelog error' do + expect { release.to_markdown }.to raise_error(Gitlab::Changelog::Error) + end + end end describe '#header_start_position' do diff --git a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb index bcc99b78805..b657f73fa77 100644 --- a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb @@ -66,6 +66,11 @@ RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do expect(build_names).not_to include('review') end + it 'when CI_DEPLOY_FREEZE is present' do + create(:ci_variable, project: project, key: 'CI_DEPLOY_FREEZE', value: 'true') + expect(build_names).to eq %w(placeholder) + end + it 'when CANARY_ENABLED' do create(:ci_variable, project: project, key: 'CANARY_ENABLED', value: 'true') diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb index abb8948f13a..73bc4a5d1f3 100644 --- a/spec/requests/api/lint_spec.rb +++ b/spec/requests/api/lint_spec.rb @@ -110,7 +110,7 @@ RSpec.describe API::Lint do context 'when authenticated' do let_it_be(:api_user) { create(:user) } - context 'with valid .gitlab-ci.yaml content' do + context 'with valid .gitlab-ci.yml content' do let(:yaml_content) do File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) end @@ -140,7 +140,7 @@ RSpec.describe API::Lint do end end - context 'with valid .gitlab-ci.yaml with warnings' do + context 'with valid .gitlab-ci.yml with warnings' do let(:yaml_content) { { job: { script: 'ls', rules: [{ when: 'always' }] } }.to_yaml } it 'passes validation but returns warnings' do @@ -153,7 +153,7 @@ RSpec.describe API::Lint do end end - context 'with valid .gitlab-ci.yaml using deprecated keywords' do + context 'with valid .gitlab-ci.yml using deprecated keywords' do let(:yaml_content) { { job: { script: 'ls', type: 'test' }, types: ['test'] }.to_yaml } it 'passes validation but returns warnings' do @@ -166,7 +166,7 @@ RSpec.describe API::Lint do end end - context 'with an invalid .gitlab_ci.yml' do + context 'with an invalid .gitlab-ci.yml' do context 'with invalid syntax' do let(:yaml_content) { 'invalid content' } @@ -384,6 +384,15 @@ RSpec.describe API::Lint do project.add_developer(api_user) end + context 'with no commit' do + it 'returns error about providing content' do + ci_lint + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['errors']).to match_array(['Please provide content of .gitlab-ci.yml']) + end + end + context 'with valid .gitlab-ci.yml content' do let(:yaml_content) do { include: { local: 'another-gitlab-ci.yml' }, test: { stage: 'test', script: 'echo 1' } }.to_yaml diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 623cf24b9cb..340ed7bde53 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -836,6 +836,24 @@ RSpec.describe 'Git HTTP requests' do end end end + + context "when the user is admin" do + let(:admin) { create(:admin) } + let(:env) { { user: admin.username, password: admin.password } } + + # Currently, the admin mode is bypassed for git operations. + # Once the admin mode is considered for git operations, this test will fail. + # Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/296509 + context 'when admin mode is enabled', :enable_admin_mode do + it_behaves_like 'pulls are allowed' + it_behaves_like 'pushes are allowed' + end + + context 'when admin mode is disabled' do + it_behaves_like 'pulls are allowed' + it_behaves_like 'pushes are allowed' + end + end end end @@ -929,10 +947,10 @@ RSpec.describe 'Git HTTP requests' do context 'when admin mode is disabled' do it_behaves_like 'can download code only' - it 'downloads from other project get status 404' do + it 'downloads from other project get status 403' do clone_get "#{other_project.full_path}.git", user: 'gitlab-ci-token', password: build.token - expect(response).to have_gitlab_http_status(:not_found) + expect(response).to have_gitlab_http_status(:forbidden) end end end @@ -1534,10 +1552,10 @@ RSpec.describe 'Git HTTP requests' do context 'when admin mode is disabled' do it_behaves_like 'can download code only' - it 'downloads from other project get status 404' do + it 'downloads from other project get status 403' do clone_get "#{other_project.full_path}.git", user: 'gitlab-ci-token', password: build.token - expect(response).to have_gitlab_http_status(:not_found) + expect(response).to have_gitlab_http_status(:forbidden) end end end diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index a6aa76c7e6c..0beb5157ed6 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -18,17 +18,35 @@ RSpec.describe Projects::DestroyService, :aggregate_failures do end shared_examples 'deleting the project' do - before do - # Run sidekiq immediately to check that renamed repository will be removed + it 'deletes the project', :sidekiq_inline do destroy_project(project, user, {}) - end - it 'deletes the project', :sidekiq_inline do expect(Project.unscoped.all).not_to include(project) expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey expect(project.gitlab_shell.repository_exists?(project.repository_storage, remove_path + '.git')).to be_falsey end + + it 'publishes a ProjectDeleted event with project id and namespace id' do + expected_data = { project_id: project.id, namespace_id: project.namespace_id } + expect(Gitlab::EventStore) + .to receive(:publish) + .with(event_type(Projects::ProjectDeletedEvent).containing(expected_data)) + + destroy_project(project, user, {}) + end + + context 'when feature flag publish_project_deleted_event is disabled' do + before do + stub_feature_flags(publish_project_deleted_event: false) + end + + it 'does not publish an event' do + expect(Gitlab::EventStore).not_to receive(:publish) + + destroy_project(project, user, {}) + end + end end shared_examples 'deleting the project with pipeline and build' do diff --git a/spec/support/matchers/event_store.rb b/spec/support/matchers/event_store.rb new file mode 100644 index 00000000000..96a71ae3c22 --- /dev/null +++ b/spec/support/matchers/event_store.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +RSpec::Matchers.define :event_type do |event_class| + match do |actual| + actual.instance_of?(event_class) && + actual.data == @expected_data + end + + chain :containing do |expected_data| + @expected_data = expected_data + end +end diff --git a/spec/support/shared_examples/features/variable_list_shared_examples.rb b/spec/support/shared_examples/features/variable_list_shared_examples.rb index 52451839281..c63faace6b2 100644 --- a/spec/support/shared_examples/features/variable_list_shared_examples.rb +++ b/spec/support/shared_examples/features/variable_list_shared_examples.rb @@ -166,7 +166,7 @@ RSpec.shared_examples 'variable list' do wait_for_requests expect(find('.flash-container')).to be_present - expect(find('.flash-text').text).to have_content('Variables key (key) has already been taken') + expect(find('[data-testid="alert-danger"]').text).to have_content('Variables key (key) has already been taken') end it 'prevents a variable to be added if no values are provided when a variable is set to masked' do diff --git a/spec/tooling/quality/test_level_spec.rb b/spec/tooling/quality/test_level_spec.rb index 8a944a473d7..33d3a5b49b3 100644 --- a/spec/tooling/quality/test_level_spec.rb +++ b/spec/tooling/quality/test_level_spec.rb @@ -28,7 +28,7 @@ RSpec.describe Quality::TestLevel do context 'when level is unit' do it 'returns a pattern' do expect(subject.pattern(:unit)) - .to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,metrics_server,models,policies,presenters,rack_servers,replicators,routing,rubocop,scripts,serializers,services,sidekiq,sidekiq_cluster,spam,support_specs,tasks,uploaders,validators,views,workers,tooling}{,/**/}*_spec.rb") + .to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,events,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,metrics_server,models,policies,presenters,rack_servers,replicators,routing,rubocop,scripts,serializers,services,sidekiq,sidekiq_cluster,spam,support_specs,tasks,uploaders,validators,views,workers,tooling}{,/**/}*_spec.rb") end end @@ -110,7 +110,7 @@ RSpec.describe Quality::TestLevel do context 'when level is unit' do it 'returns a regexp' do expect(subject.regexp(:unit)) - .to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|metrics_server|models|policies|presenters|rack_servers|replicators|routing|rubocop|scripts|serializers|services|sidekiq|sidekiq_cluster|spam|support_specs|tasks|uploaders|validators|views|workers|tooling)}) + .to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|events|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|metrics_server|models|policies|presenters|rack_servers|replicators|routing|rubocop|scripts|serializers|services|sidekiq|sidekiq_cluster|spam|support_specs|tasks|uploaders|validators|views|workers|tooling)}) end end diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb index a21cf3ed5f6..1cd5d23d8fc 100644 --- a/spec/workers/every_sidekiq_worker_spec.rb +++ b/spec/workers/every_sidekiq_worker_spec.rb @@ -349,6 +349,7 @@ RSpec.describe 'Every Sidekiq worker' do 'Namespaces::OnboardingPipelineCreatedWorker' => 3, 'Namespaces::OnboardingProgressWorker' => 3, 'Namespaces::OnboardingUserAddedWorker' => 3, + 'Namespaces::RefreshRootStatisticsWorker' => 3, 'Namespaces::RootStatisticsWorker' => 3, 'Namespaces::ScheduleAggregationWorker' => 3, 'NetworkPolicyMetricsWorker' => 3, diff --git a/spec/workers/namespaces/update_root_statistics_worker_spec.rb b/spec/workers/namespaces/update_root_statistics_worker_spec.rb new file mode 100644 index 00000000000..a525904b757 --- /dev/null +++ b/spec/workers/namespaces/update_root_statistics_worker_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Namespaces::UpdateRootStatisticsWorker do + let(:namespace_id) { 123 } + + let(:event) do + Projects::ProjectDeletedEvent.new(data: { project_id: 1, namespace_id: namespace_id }) + end + + subject { consume_event(event) } + + def consume_event(event) + described_class.new.perform(event.class.name, event.data) + end + + it 'enqueues ScheduleAggregationWorker' do + expect(Namespaces::ScheduleAggregationWorker).to receive(:perform_async).with(namespace_id) + + subject + end +end |