diff options
22 files changed, 114 insertions, 182 deletions
diff --git a/app/assets/javascripts/security_configuration/components/feature_card.vue b/app/assets/javascripts/security_configuration/components/feature_card.vue index 0ecfdf420db..86afdbfeb8c 100644 --- a/app/assets/javascripts/security_configuration/components/feature_card.vue +++ b/app/assets/javascripts/security_configuration/components/feature_card.vue @@ -128,6 +128,7 @@ export default { variant="confirm" category="primary" class="gl-mt-5" + :data-qa-selector="`${feature.type}_mr_button`" /> <gl-button diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js index 7421f6c8ea6..2fd1e17aaa6 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js @@ -10,20 +10,23 @@ export default { render(h) { if (extensions.length === 0) return null; - return h( - 'div', - {}, - extensions.map((extension) => - h(extension, { - props: extension.props.reduce( - (acc, key) => ({ - ...acc, - [key]: this.mr[key], - }), - {}, - ), - }), + return h('div', {}, [ + ...extensions.map((extension) => + h( + { ...extension }, + { + props: { + ...extension.props.reduce( + (acc, key) => ({ + ...acc, + [key]: this.mr[key], + }), + {}, + ), + }, + }, + ), ), - ); + ]); }, }; diff --git a/app/assets/javascripts/vue_shared/security_configuration/components/manage_via_mr.vue b/app/assets/javascripts/vue_shared/security_configuration/components/manage_via_mr.vue index 0ff858e6afc..42272c222fc 100644 --- a/app/assets/javascripts/vue_shared/security_configuration/components/manage_via_mr.vue +++ b/app/assets/javascripts/vue_shared/security_configuration/components/manage_via_mr.vue @@ -100,6 +100,7 @@ export default { :loading="isLoading" :variant="variant" :category="category" + :data-qa-selector="`${feature.type}_mr_button`" @click="mutate" >{{ $options.i18n.buttonLabel }}</gl-button > diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index cf15433f2e5..c0bb5eba871 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -477,8 +477,6 @@ module ApplicationSettingsHelper end def pending_user_count - return 0 if Gitlab::CurrentSettings.new_user_signups_cap.blank? - User.blocked_pending_approval.count end end diff --git a/app/views/admin/application_settings/appearances/preview_sign_in.html.haml b/app/views/admin/application_settings/appearances/preview_sign_in.html.haml index 77c37abbeef..2e4ab714048 100644 --- a/app/views/admin/application_settings/appearances/preview_sign_in.html.haml +++ b/app/views/admin/application_settings/appearances/preview_sign_in.html.haml @@ -1,12 +1,13 @@ = render 'devise/shared/tab_single', tab_title: _('Sign in preview') .login-box %form.gl-show-field-errors + - title = _('This form is disabled in preview') .form-group = label_tag :login - = text_field_tag :login, nil, class: "form-control gl-form-input top", title: _('Please provide your username or email address.') + = text_field_tag :login, nil, disabled: true, class: "form-control gl-form-input top", title: title .form-group = label_tag :password - = password_field_tag :password, nil, class: "form-control gl-form-input bottom", title: _('This field is required.') + = password_field_tag :password, nil, disabled: true, class: "form-control gl-form-input bottom", title: title .form-group - = button_tag _("Sign in"), class: "btn gl-button btn-confirm", type: "button" + = button_tag _("Sign in"), disabled: true, class: "btn gl-button btn-confirm", type: "button", title: title diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml index d0b73a3364a..13f2bf0019e 100644 --- a/app/views/layouts/nav/sidebar/_admin.html.haml +++ b/app/views/layouts/nav/sidebar/_admin.html.haml @@ -1,7 +1,7 @@ %aside.nav-sidebar.qa-admin-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), 'aria-label': _('Admin navigation') } .nav-sidebar-inner-scroll .context-header - = link_to admin_root_path, title: _('Admin Overview') do + = link_to admin_root_path, title: _('Admin Overview'), class: 'has-tooltip', data: { container: 'body', placement: 'right' } do %span{ class: ['avatar-container', 'settings-avatar', 'rect-avatar', 's32'] } = sprite_icon('admin', size: 18) %span.sidebar-context-title diff --git a/app/views/layouts/nav/sidebar/_profile.html.haml b/app/views/layouts/nav/sidebar/_profile.html.haml index 4db1e532ba5..16c0c00ad3f 100644 --- a/app/views/layouts/nav/sidebar/_profile.html.haml +++ b/app/views/layouts/nav/sidebar/_profile.html.haml @@ -1,7 +1,7 @@ %aside.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **sidebar_tracking_attributes_by_object(current_user), 'aria-label': _('User settings') } .nav-sidebar-inner-scroll .context-header - = link_to profile_path, title: _('Profile Settings') do + = link_to profile_path, title: _('Profile Settings'), class: 'has-tooltip', data: { container: 'body', placement: 'right' } do %span{ class: ['avatar-container', 'settings-avatar', 's32'] } = image_tag avatar_icon_for_user(current_user, 32), class: ['avatar', 'avatar-tile', 'js-sidebar-user-avatar', 's32'], alt: current_user.name, data: { testid: 'sidebar-user-avatar' } %span.sidebar-context-title= _('User Settings') diff --git a/doc/ci/examples/deployment/index.md b/doc/ci/examples/deployment/index.md index 14fb77dc49f..8d01888f829 100644 --- a/doc/ci/examples/deployment/index.md +++ b/doc/ci/examples/deployment/index.md @@ -69,7 +69,7 @@ staging: - gem install dpl - dpl --provider=heroku --app=my-app-staging --api_key=$HEROKU_STAGING_API_KEY only: - - master + - main ``` The first line `apt-get update -yq` updates the list of available packages, @@ -81,7 +81,7 @@ The above example is valid for all Debian-compatible systems. It's pretty common in the development workflow to have staging (development) and production environments -Let's consider the following example: we would like to deploy the `master` +Let's consider the following example: we would like to deploy the `main` branch to `staging` and all tags to the `production` environment. The final `.gitlab-ci.yml` for that setup would look like this: @@ -92,7 +92,7 @@ staging: - gem install dpl - dpl --provider=heroku --app=my-app-staging --api_key=$HEROKU_STAGING_API_KEY only: - - master + - main production: stage: deploy @@ -105,7 +105,7 @@ production: We created two deploy jobs that are executed on different events: -- `staging`: Executed for all commits pushed to the `master` branch +- `staging`: Executed for all commits pushed to the `main` branch - `production`: Executed for all pushed tags We also use two secure variables: diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md index 9abf993f0fe..67559db2cf0 100644 --- a/doc/update/upgrading_from_source.md +++ b/doc/update/upgrading_from_source.md @@ -258,6 +258,10 @@ sudo systemctl daemon-reload ### 10. Install libraries, migrations, etc +Make sure you have the required +[PostgreSQL extensions](../install/requirements.md#postgresql-requirements), +then proceed to install the needed libraries: + ```shell cd /home/git/gitlab @@ -304,15 +308,18 @@ cd /home/git/gitlab sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production ``` +NOTE: +If you get any errors concerning Rack attack, see the [13.0](#1301) specific +upgrade instructions. + ### 13. Update Gitaly #### Compile Gitaly ```shell -cd /home/git/gitaly -sudo -u git -H git fetch --all --tags --prune -sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION) -sudo -u git -H make +# Fetch Gitaly source with Git and compile with Go +cd /home/git/gitlab +sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories]" RAILS_ENV=production ``` ### 14. Update GitLab Pages @@ -375,14 +382,13 @@ Additional instructions here. ### 13.0.1 -As part of [deprecating Rack Attack throttles on Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/4750), Rack Attack initializer on GitLab +As part of [deprecating Rack Attack throttles on Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/4750), the Rack Attack initializer on GitLab was renamed from [`config/initializers/rack_attack_new.rb` to `config/initializers/rack_attack.rb`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/33072). If this file exists on your installation, consider creating a backup before updating: ```shell cd /home/git/gitlab - -cp config/initializers/rack_attack.rb config/initializers/rack_attack_backup.rb +cp config/initializers/rack_attack.rb ~/config/initializers/rack_attack_backup.rb ``` ## Troubleshooting diff --git a/doc/user/application_security/api_fuzzing/create_har_files.md b/doc/user/application_security/api_fuzzing/create_har_files.md index 7940e072420..db0b2a32bcf 100644 --- a/doc/user/application_security/api_fuzzing/create_har_files.md +++ b/doc/user/application_security/api_fuzzing/create_har_files.md @@ -68,8 +68,12 @@ Install GitLab HAR Recorder: 1. Make sure proxy is used! 1. Stop the recorder. -To verify the HAR contains all requests, use the [HAR Viewer (online)](http://www.softwareishard.com/har/viewer/). -[Google Admin Toolbox HAR Analyzer](https://toolbox.googleapps.com/apps/har_analyzer/) +To verify the HAR contains all requests, use an online HAR viewer, for example: + +- [HAR Viewer](http://www.softwareishard.com/har/viewer/) +<!-- vale gitlab.Admin = NO --> +- [Google Admin Toolbox HAR Analyzer](https://toolbox.googleapps.com/apps/har_analyzer/) +<!-- vale gitlab.Admin = YES --> ### Insomnia API Client @@ -190,7 +194,9 @@ a text editor. Tools recommended for viewing HAR files include: - [HAR Viewer](http://www.softwareishard.com/har/viewer/) - (online) +<!-- vale gitlab.Admin = NO --> - [Google Admin Toolbox HAR Analyzer](https://toolbox.googleapps.com/apps/har_analyzer/) - (online) +<!-- vale gitlab.Admin = YES --> - [Fiddler](https://www.telerik.com/fiddler) - local - [Insomnia API Client](https://insomnia.rest/) - local diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md index 94656c91e98..45c2f1aaf04 100644 --- a/doc/user/project/pages/introduction.md +++ b/doc/user/project/pages/introduction.md @@ -118,7 +118,7 @@ pages: paths: - public only: - - master + - main ``` ### `.gitlab-ci.yml` for a static site generator @@ -133,7 +133,7 @@ the `pages` job with the [`only` parameter](../../../ci/yaml/index.md#only--exce whenever a new commit is pushed to a branch used specifically for your pages. -That way, you can have your project's code in the `master` branch and use an +That way, you can have your project's code in the `main` branch and use an orphan branch (let's name it `pages`) to host your static generator site. You can create a new empty branch like this: @@ -163,7 +163,7 @@ pages: - pages ``` -See an example that has different files in the [`master` branch](https://gitlab.com/pages/jekyll-branched/tree/master) +See an example that has different files in the [`main` branch](https://gitlab.com/pages/jekyll-branched/tree/main) and the source files for Jekyll are in a [`pages` branch](https://gitlab.com/pages/jekyll-branched/tree/pages) which also includes `.gitlab-ci.yml`. diff --git a/lib/sidebars/groups/menus/scope_menu.rb b/lib/sidebars/groups/menus/scope_menu.rb index 02c359e3c99..6ce43491343 100644 --- a/lib/sidebars/groups/menus/scope_menu.rb +++ b/lib/sidebars/groups/menus/scope_menu.rb @@ -21,7 +21,11 @@ module Sidebars override :extra_nav_link_html_options def extra_nav_link_html_options - { class: 'context-header' } + { + class: 'context-header has-tooltip', + title: context.group.name, + data: { container: 'body', placement: 'right' } + } end override :render? diff --git a/lib/sidebars/projects/menus/scope_menu.rb b/lib/sidebars/projects/menus/scope_menu.rb index 539912aa77b..35502c7ea09 100644 --- a/lib/sidebars/projects/menus/scope_menu.rb +++ b/lib/sidebars/projects/menus/scope_menu.rb @@ -28,7 +28,11 @@ module Sidebars override :extra_nav_link_html_options def extra_nav_link_html_options - { class: 'context-header' } + { + class: 'context-header has-tooltip', + title: context.project.name, + data: { container: 'body', placement: 'right' } + } end override :render? diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 0bd07235078..2d92e242378 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -25370,9 +25370,6 @@ msgstr "" msgid "Please provide attributes to update" msgstr "" -msgid "Please provide your username or email address." -msgstr "" - msgid "Please reach out if you have any questions and we'll be happy to assist." msgstr "" @@ -34536,6 +34533,9 @@ msgstr "" msgid "This field is required." msgstr "" +msgid "This form is disabled in preview" +msgstr "" + msgid "This group" msgstr "" diff --git a/qa/qa/page/merge_request/new.rb b/qa/qa/page/merge_request/new.rb index 71e51ddd504..bcc60a8275d 100644 --- a/qa/qa/page/merge_request/new.rb +++ b/qa/qa/page/merge_request/new.rb @@ -20,6 +20,13 @@ module QA element :file_name_content end + def has_secure_description?(scanner_name) + scanner_url_name = scanner_name.downcase.tr('_', '-') + "Configure #{scanner_name} in `.gitlab-ci.yml` using the GitLab managed template. You can " \ + "[add variable overrides](https://docs.gitlab.com/ee/user/application_security/#{scanner_url_name}/#customizing-the-#{scanner_url_name}-settings) " \ + "to customize #{scanner_name} settings." + end + def create_merge_request click_element(:issuable_create_button, Page::MergeRequest::Show) end diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index 1d8d9ed6859..7de99a11cf6 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -288,13 +288,11 @@ module QA end def merge_immediately! - merge_moment_dropdown_found = has_element?(:merge_moment_dropdown, wait: 0) - - if merge_moment_dropdown_found - click_element(:merge_moment_dropdown) - click_element(:merge_immediately_menu_item) + if has_element?(:merge_moment_dropdown) + click_element(:merge_moment_dropdown, skip_finished_loading_check: true) + click_element(:merge_immediately_menu_item, skip_finished_loading_check: true) else - click_element(:merge_button) + click_element(:merge_button, skip_finished_loading_check: true) end end diff --git a/qa/qa/page/project/secure/configuration_form.rb b/qa/qa/page/project/secure/configuration_form.rb index 73d1601b61e..3e89a57e870 100644 --- a/qa/qa/page/project/secure/configuration_form.rb +++ b/qa/qa/page/project/secure/configuration_form.rb @@ -9,19 +9,31 @@ module QA include QA::Page::Settings::Common view 'app/assets/javascripts/security_configuration/components/feature_card.vue' do + element :dependency_scanning_status, "`${feature.type}_status`" # rubocop:disable QA/ElementWithPattern element :sast_status, "`${feature.type}_status`" # rubocop:disable QA/ElementWithPattern element :sast_enable_button, "`${feature.type}_enable_button`" # rubocop:disable QA/ElementWithPattern + element :dependency_scanning_mr_button, "`${feature.type}_mr_button`" # rubocop:disable QA/ElementWithPattern end def click_sast_enable_button click_element(:sast_enable_button) end + def click_dependency_scanning_mr_button + click_element(:dependency_scanning_mr_button) + end + def has_sast_status?(status_text) within_element(:sast_status) do has_text?(status_text) end end + + def has_dependency_scanning_status?(status_text) + within_element(:dependency_scanning_status) do + has_text?(status_text) + end + end end end end diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb index cd148642b90..cb69eac8035 100644 --- a/spec/features/admin/admin_appearance_spec.rb +++ b/spec/features/admin/admin_appearance_spec.rb @@ -34,6 +34,10 @@ RSpec.describe 'Admin Appearance' do visit admin_application_settings_appearances_path click_link "Sign-in page" + expect(find('#login')).to be_disabled + expect(find('#password')).to be_disabled + expect(find('button')).to be_disabled + expect_custom_sign_in_appearance(appearance) end diff --git a/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js b/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js index 4bb22feb913..5b4f954b672 100644 --- a/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js +++ b/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js @@ -35,9 +35,6 @@ describe('Signup Form', () => { const findDenyListRawInputGroup = () => wrapper.findByTestId('domain-denylist-raw-input-group'); const findDenyListFileInputGroup = () => wrapper.findByTestId('domain-denylist-file-input-group'); - - const findRequireAdminApprovalCheckbox = () => - wrapper.findByTestId('require-admin-approval-checkbox'); const findUserCapInput = () => wrapper.findByTestId('user-cap-input'); const findModal = () => wrapper.find(GlModal); @@ -191,125 +188,6 @@ describe('Signup Form', () => { }); describe('form submit button confirmation modal for side-effect of adding possibly unwanted new users', () => { - it.each` - requireAdminApprovalAction | userCapAction | pendingUserCount | buttonEffect - ${'unchanged from true'} | ${'unchanged'} | ${0} | ${'submits form'} - ${'unchanged from false'} | ${'unchanged'} | ${0} | ${'submits form'} - ${'toggled off'} | ${'unchanged'} | ${1} | ${'shows confirmation modal'} - ${'toggled off'} | ${'unchanged'} | ${0} | ${'submits form'} - ${'toggled on'} | ${'unchanged'} | ${0} | ${'submits form'} - ${'unchanged from false'} | ${'increased'} | ${1} | ${'shows confirmation modal'} - ${'unchanged from true'} | ${'increased'} | ${0} | ${'submits form'} - ${'toggled off'} | ${'increased'} | ${1} | ${'shows confirmation modal'} - ${'toggled off'} | ${'increased'} | ${0} | ${'submits form'} - ${'toggled on'} | ${'increased'} | ${1} | ${'shows confirmation modal'} - ${'toggled on'} | ${'increased'} | ${0} | ${'submits form'} - ${'toggled on'} | ${'decreased'} | ${0} | ${'submits form'} - ${'toggled on'} | ${'decreased'} | ${1} | ${'submits form'} - ${'unchanged from false'} | ${'changed from limited to unlimited'} | ${1} | ${'shows confirmation modal'} - ${'unchanged from false'} | ${'changed from limited to unlimited'} | ${0} | ${'submits form'} - ${'unchanged from false'} | ${'changed from unlimited to limited'} | ${0} | ${'submits form'} - ${'unchanged from false'} | ${'unchanged from unlimited'} | ${0} | ${'submits form'} - `( - '$buttonEffect if require admin approval for new sign-ups is $requireAdminApprovalAction and the user cap is $userCapAction and pending user count is $pendingUserCount', - async ({ requireAdminApprovalAction, userCapAction, pendingUserCount, buttonEffect }) => { - let isModalDisplayed; - - switch (buttonEffect) { - case 'shows confirmation modal': - isModalDisplayed = true; - break; - case 'submits form': - isModalDisplayed = false; - break; - default: - isModalDisplayed = false; - break; - } - - const isFormSubmittedWhenClickingFormSubmitButton = !isModalDisplayed; - - const injectedProps = { - pendingUserCount, - }; - - const USER_CAP_DEFAULT = 5; - - switch (userCapAction) { - case 'changed from unlimited to limited': - injectedProps.newUserSignupsCap = ''; - break; - case 'unchanged from unlimited': - injectedProps.newUserSignupsCap = ''; - break; - default: - injectedProps.newUserSignupsCap = USER_CAP_DEFAULT; - break; - } - - switch (requireAdminApprovalAction) { - case 'unchanged from true': - injectedProps.requireAdminApprovalAfterUserSignup = true; - break; - case 'unchanged from false': - injectedProps.requireAdminApprovalAfterUserSignup = false; - break; - case 'toggled off': - injectedProps.requireAdminApprovalAfterUserSignup = true; - break; - case 'toggled on': - injectedProps.requireAdminApprovalAfterUserSignup = false; - break; - default: - injectedProps.requireAdminApprovalAfterUserSignup = false; - break; - } - - formSubmitSpy = jest.spyOn(HTMLFormElement.prototype, 'submit').mockImplementation(); - - await mountComponent({ - injectedProps, - stubs: { GlButton, GlModal: stubComponent(GlModal) }, - }); - - findModal().vm.show = jest.fn(); - - if ( - requireAdminApprovalAction === 'toggled off' || - requireAdminApprovalAction === 'toggled on' - ) { - await findRequireAdminApprovalCheckbox().vm.$emit('input', false); - } - - switch (userCapAction) { - case 'increased': - await findUserCapInput().vm.$emit('input', USER_CAP_DEFAULT + 1); - break; - case 'decreased': - await findUserCapInput().vm.$emit('input', USER_CAP_DEFAULT - 1); - break; - case 'changed from limited to unlimited': - await findUserCapInput().vm.$emit('input', ''); - break; - case 'changed from unlimited to limited': - await findUserCapInput().vm.$emit('input', USER_CAP_DEFAULT); - break; - default: - break; - } - - await findFormSubmitButton().trigger('click'); - - if (isFormSubmittedWhenClickingFormSubmitButton) { - expect(formSubmitSpy).toHaveBeenCalled(); - expect(findModal().vm.show).not.toHaveBeenCalled(); - } else { - expect(formSubmitSpy).not.toHaveBeenCalled(); - expect(findModal().vm.show).toHaveBeenCalled(); - } - }, - ); - describe('modal actions', () => { beforeEach(async () => { const INITIAL_USER_CAP = 5; diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb index ef5f6931d02..ef5c99efdd3 100644 --- a/spec/helpers/application_settings_helper_spec.rb +++ b/spec/helpers/application_settings_helper_spec.rb @@ -271,18 +271,6 @@ RSpec.describe ApplicationSettingsHelper do expect(pending_user_count).to eq 1 end end - - context 'when the new_user_signups_cap is not present' do - let(:user_cap) { nil } - - it { is_expected.to eq 0 } - - it 'does not query users unnecessarily' do - expect(User).not_to receive(:blocked_pending_approval) - - pending_user_count - end - end end describe '#sidekiq_job_limiter_modes_for_select' do diff --git a/spec/lib/sidebars/groups/menus/scope_menu_spec.rb b/spec/lib/sidebars/groups/menus/scope_menu_spec.rb new file mode 100644 index 00000000000..4b77a09117a --- /dev/null +++ b/spec/lib/sidebars/groups/menus/scope_menu_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Sidebars::Groups::Menus::ScopeMenu do + let(:group) { build(:group) } + let(:user) { group.owner } + let(:context) { Sidebars::Groups::Context.new(current_user: user, container: group) } + + describe '#extra_nav_link_html_options' do + subject { described_class.new(context).extra_nav_link_html_options } + + specify { is_expected.to match(hash_including(class: 'context-header has-tooltip', title: context.group.name)) } + end +end diff --git a/spec/lib/sidebars/projects/menus/scope_menu_spec.rb b/spec/lib/sidebars/projects/menus/scope_menu_spec.rb index 5040ef9b0ff..980ab2f7c71 100644 --- a/spec/lib/sidebars/projects/menus/scope_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/scope_menu_spec.rb @@ -12,4 +12,10 @@ RSpec.describe Sidebars::Projects::Menus::ScopeMenu do specify { is_expected.to match(hash_including(class: 'shortcuts-project rspec-project-link')) } end + + describe '#extra_nav_link_html_options' do + subject { described_class.new(context).extra_nav_link_html_options } + + specify { is_expected.to match(hash_including(class: 'context-header has-tooltip', title: context.project.name)) } + end end |