From 43a25d93ebdabea52f99b05e15b06250cd8f07d7 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 17 May 2023 16:05:49 +0000 Subject: Add latest changes from gitlab-org/gitlab@16-0-stable-ee --- spec/helpers/abuse_reports_helper_spec.rb | 13 + spec/helpers/access_tokens_helper_spec.rb | 2 +- spec/helpers/admin/abuse_reports_helper_spec.rb | 34 ++ .../analytics/cycle_analytics_helper_spec.rb | 61 --- spec/helpers/application_helper_spec.rb | 80 +++- spec/helpers/application_settings_helper_spec.rb | 74 +--- spec/helpers/artifacts_helper_spec.rb | 1 + spec/helpers/avatars_helper_spec.rb | 159 +++++--- spec/helpers/blame_helper_spec.rb | 12 +- spec/helpers/blob_helper_spec.rb | 26 +- spec/helpers/broadcast_messages_helper_spec.rb | 79 ++-- spec/helpers/ci/builds_helper_spec.rb | 59 --- spec/helpers/ci/catalog/resources_helper_spec.rb | 35 ++ spec/helpers/ci/jobs_helper_spec.rb | 53 ++- spec/helpers/ci/pipeline_editor_helper_spec.rb | 7 +- spec/helpers/ci/pipelines_helper_spec.rb | 36 +- spec/helpers/ci/runners_helper_spec.rb | 38 +- spec/helpers/ci/variables_helper_spec.rb | 2 +- spec/helpers/clusters_helper_spec.rb | 84 ++++ spec/helpers/commits_helper_spec.rb | 15 +- spec/helpers/device_registration_helper_spec.rb | 37 ++ spec/helpers/diff_helper_spec.rb | 59 ++- spec/helpers/emoji_helper_spec.rb | 22 +- spec/helpers/environment_helper_spec.rb | 18 +- spec/helpers/environments_helper_spec.rb | 18 +- spec/helpers/explore_helper_spec.rb | 2 +- spec/helpers/feature_flags_helper_spec.rb | 14 +- spec/helpers/groups/observability_helper_spec.rb | 91 ++--- spec/helpers/groups_helper_spec.rb | 7 +- spec/helpers/ide_helper_spec.rb | 191 +++++----- spec/helpers/integrations_helper_spec.rb | 7 +- spec/helpers/issuables_helper_spec.rb | 117 +++++- spec/helpers/issues_helper_spec.rb | 26 +- spec/helpers/jira_connect_helper_spec.rb | 35 +- spec/helpers/labels_helper_spec.rb | 14 +- spec/helpers/markup_helper_spec.rb | 39 ++ spec/helpers/merge_requests_helper_spec.rb | 72 ++++ spec/helpers/namespaces_helper_spec.rb | 37 +- spec/helpers/nav/new_dropdown_helper_spec.rb | 71 ++-- spec/helpers/nav/top_nav_helper_spec.rb | 58 +-- spec/helpers/nav_helper_spec.rb | 76 ++-- spec/helpers/notes_helper_spec.rb | 13 +- spec/helpers/notify_helper_spec.rb | 17 +- spec/helpers/packages_helper_spec.rb | 140 ++++++- spec/helpers/page_layout_helper_spec.rb | 21 +- spec/helpers/plan_limits_helper_spec.rb | 28 ++ spec/helpers/profiles_helper_spec.rb | 44 ++- .../helpers/projects/ml/experiments_helper_spec.rb | 48 +-- spec/helpers/projects/pipeline_helper_spec.rb | 3 +- .../projects/settings/branch_rules_helper_spec.rb | 25 ++ spec/helpers/projects_helper_spec.rb | 190 +++++++++- spec/helpers/protected_refs_helper_spec.rb | 51 +++ spec/helpers/registrations_helper_spec.rb | 16 - .../routing/pseudonymization_helper_spec.rb | 228 ++++++----- spec/helpers/safe_format_helper_spec.rb | 41 ++ spec/helpers/search_helper_spec.rb | 55 +++ spec/helpers/sessions_helper_spec.rb | 34 +- spec/helpers/sidebars_helper_spec.rb | 422 +++++++++++++++++++-- spec/helpers/sorting_helper_spec.rb | 54 +++ spec/helpers/storage_helper_spec.rb | 28 +- spec/helpers/todos_helper_spec.rb | 60 ++- spec/helpers/tree_helper_spec.rb | 1 + spec/helpers/users/callouts_helper_spec.rb | 70 ++-- spec/helpers/users/group_callouts_helper_spec.rb | 10 +- spec/helpers/users_helper_spec.rb | 103 ++++- spec/helpers/version_check_helper_spec.rb | 30 ++ spec/helpers/visibility_level_helper_spec.rb | 25 +- spec/helpers/work_items_helper_spec.rb | 24 ++ 68 files changed, 2609 insertions(+), 1053 deletions(-) create mode 100644 spec/helpers/abuse_reports_helper_spec.rb create mode 100644 spec/helpers/admin/abuse_reports_helper_spec.rb delete mode 100644 spec/helpers/analytics/cycle_analytics_helper_spec.rb create mode 100644 spec/helpers/ci/catalog/resources_helper_spec.rb create mode 100644 spec/helpers/device_registration_helper_spec.rb create mode 100644 spec/helpers/plan_limits_helper_spec.rb create mode 100644 spec/helpers/projects/settings/branch_rules_helper_spec.rb create mode 100644 spec/helpers/protected_refs_helper_spec.rb create mode 100644 spec/helpers/safe_format_helper_spec.rb create mode 100644 spec/helpers/work_items_helper_spec.rb (limited to 'spec/helpers') diff --git a/spec/helpers/abuse_reports_helper_spec.rb b/spec/helpers/abuse_reports_helper_spec.rb new file mode 100644 index 00000000000..6d381b7eb56 --- /dev/null +++ b/spec/helpers/abuse_reports_helper_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe AbuseReportsHelper, feature_category: :insider_threat do + describe '#valid_image_mimetypes' do + subject(:valid_image_mimetypes) { helper.valid_image_mimetypes } + + it { + is_expected.to eq('image/png, image/jpg, image/jpeg, image/gif, image/bmp, image/tiff, image/ico or image/webp') + } + end +end diff --git a/spec/helpers/access_tokens_helper_spec.rb b/spec/helpers/access_tokens_helper_spec.rb index d34251d03db..a466b2a0d3b 100644 --- a/spec/helpers/access_tokens_helper_spec.rb +++ b/spec/helpers/access_tokens_helper_spec.rb @@ -37,7 +37,7 @@ RSpec.describe AccessTokensHelper do disable_feed_token: false, static_objects_external_storage_enabled?: true ) - allow(Gitlab::IncomingEmail).to receive(:supports_issue_creation?).and_return(true) + allow(Gitlab::Email::IncomingEmail).to receive(:supports_issue_creation?).and_return(true) allow(helper).to receive_messages( current_user: user, reset_feed_token_profile_path: feed_token_reset_path, diff --git a/spec/helpers/admin/abuse_reports_helper_spec.rb b/spec/helpers/admin/abuse_reports_helper_spec.rb new file mode 100644 index 00000000000..496b7361b6e --- /dev/null +++ b/spec/helpers/admin/abuse_reports_helper_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Admin::AbuseReportsHelper, feature_category: :insider_threat do + describe '#abuse_reports_list_data' do + let!(:report) { create(:abuse_report) } # rubocop:disable RSpec/FactoryBot/AvoidCreate + let(:reports) { AbuseReport.all.page(1) } + let(:data) do + data = helper.abuse_reports_list_data(reports)[:abuse_reports_data] + Gitlab::Json.parse(data) + end + + it 'has expected attributes', :aggregate_failures do + expect(data['pagination']).to include( + "current_page" => 1, + "per_page" => 20, + "total_items" => 1 + ) + expect(data['reports'].first).to include("category", "updated_at", "reported_user", "reporter") + expect(data['categories']).to match_array(AbuseReport.categories.keys) + end + end + + describe '#abuse_report_data' do + let(:report) { build_stubbed(:abuse_report) } + + subject(:data) { helper.abuse_report_data(report)[:abuse_report_data] } + + it 'has the expected attributes' do + expect(data).to include('user', 'reporter', 'report', 'actions') + end + end +end diff --git a/spec/helpers/analytics/cycle_analytics_helper_spec.rb b/spec/helpers/analytics/cycle_analytics_helper_spec.rb deleted file mode 100644 index d906646e25c..00000000000 --- a/spec/helpers/analytics/cycle_analytics_helper_spec.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true -require "spec_helper" - -RSpec.describe Analytics::CycleAnalyticsHelper do - describe '#cycle_analytics_initial_data' do - let(:user) { create(:user, name: 'fake user', username: 'fake_user') } - let(:image_path_keys) { [:empty_state_svg_path, :no_data_svg_path, :no_access_svg_path] } - let(:api_path_keys) { [:milestones_path, :labels_path] } - let(:additional_data_keys) { [:full_path, :group_id, :group_path, :project_id, :request_path] } - let(:group) { create(:group) } - - subject(:cycle_analytics_data) { helper.cycle_analytics_initial_data(project, group) } - - before do - project.add_maintainer(user) - end - - context 'when a group is present' do - let(:project) { create(:project, group: group) } - - it "sets the correct data keys" do - expect(cycle_analytics_data.keys) - .to match_array(api_path_keys + image_path_keys + additional_data_keys) - end - - it "sets group paths" do - expect(cycle_analytics_data) - .to include({ - full_path: project.full_path, - group_path: "/#{project.namespace.name}", - group_id: project.namespace.id, - request_path: "/#{project.full_path}/-/value_stream_analytics", - milestones_path: "/groups/#{group.name}/-/milestones.json", - labels_path: "/groups/#{group.name}/-/labels.json" - }) - end - end - - context 'when a group is not present' do - let(:group) { nil } - let(:project) { create(:project) } - - it "sets the correct data keys" do - expect(cycle_analytics_data.keys) - .to match_array(image_path_keys + api_path_keys + additional_data_keys) - end - - it "sets project name space paths" do - expect(cycle_analytics_data) - .to include({ - full_path: project.full_path, - group_path: project.namespace.path, - group_id: project.namespace.id, - request_path: "/#{project.full_path}/-/value_stream_analytics", - milestones_path: "/#{project.full_path}/-/milestones.json", - labels_path: "/#{project.full_path}/-/labels.json" - }) - end - end - end -end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index bb1a4d57cc0..e9b0c900867 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -451,7 +451,7 @@ RSpec.describe ApplicationHelper do find_file: nil, group: nil, project_id: project.id, - project: project.name, + project: project.path, namespace_id: project.namespace.id } ) @@ -469,7 +469,7 @@ RSpec.describe ApplicationHelper do find_file: nil, group: project.group.name, project_id: project.id, - project: project.name, + project: project.path, namespace_id: project.namespace.id } ) @@ -495,7 +495,7 @@ RSpec.describe ApplicationHelper do find_file: nil, group: nil, project_id: issue.project.id, - project: issue.project.name, + project: issue.project.path, namespace_id: issue.project.namespace.id } ) @@ -696,14 +696,84 @@ RSpec.describe ApplicationHelper do end describe 'stylesheet_link_tag_defer' do - it 'uses print stylesheet by default' do + it 'uses print stylesheet when feature flag disabled' do + stub_feature_flags(remove_startup_css: false) + expect(helper.stylesheet_link_tag_defer('test')).to eq( '') end + it 'uses regular stylesheet when feature flag enabled' do + stub_feature_flags(remove_startup_css: true) + + expect(helper.stylesheet_link_tag_defer('test')).to eq( '') + end + it 'uses regular stylesheet when no_startup_css param present' do allow(helper.controller).to receive(:params).and_return({ no_startup_css: '' }) - expect(helper.stylesheet_link_tag_defer('test')).to eq( '') + expect(helper.stylesheet_link_tag_defer('test')).to eq( '') + end + end + + describe 'sign_in_with_redirect?' do + context 'when on the sign-in page that redirects afterwards' do + before do + allow(helper).to receive(:current_page?).and_return(true) + session[:user_return_to] = true + end + + it 'returns true' do + expect(helper.sign_in_with_redirect?).to be_truthy + end + end + + context 'when on a non sign-in page' do + before do + allow(helper).to receive(:current_page?).and_return(false) + end + + it 'returns false' do + expect(helper.sign_in_with_redirect?).to be_falsey + end + end + end + + describe 'collapsed_super_sidebar?' do + context 'when @force_desktop_expanded_sidebar is true' do + before do + helper.instance_variable_set(:@force_desktop_expanded_sidebar, true) + end + + it 'returns false' do + expect(helper.collapsed_super_sidebar?).to eq(false) + end + + it 'does not use the cookie value' do + expect(helper).not_to receive(:cookies) + helper.collapsed_super_sidebar? + end + end + + context 'when @force_desktop_expanded_sidebar is not set (default)' do + context 'when super_sidebar_collapsed cookie is true' do + before do + helper.request.cookies['super_sidebar_collapsed'] = 'true' + end + + it 'returns true' do + expect(helper.collapsed_super_sidebar?).to eq(true) + end + end + + context 'when super_sidebar_collapsed cookie is false' do + before do + helper.request.cookies['super_sidebar_collapsed'] = 'false' + end + + it 'returns false' do + expect(helper.collapsed_super_sidebar?).to eq(false) + end + end end end end diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb index 19cb970553b..f924704ab54 100644 --- a/spec/helpers/application_settings_helper_spec.rb +++ b/spec/helpers/application_settings_helper_spec.rb @@ -68,11 +68,13 @@ RSpec.describe ApplicationSettingsHelper do )) end - context 'when GitLab.com' do - before do - allow(Gitlab).to receive(:com?).and_return(true) - end + it 'contains GitLab for Slack app parameters' do + params = %i(slack_app_enabled slack_app_id slack_app_secret slack_app_signing_secret slack_app_verification_token) + expect(helper.visible_attributes).to include(*params) + end + + context 'when on SaaS', :saas do it 'does not contain :deactivate_dormant_users' do expect(helper.visible_attributes).not_to include(:deactivate_dormant_users) end @@ -102,70 +104,6 @@ RSpec.describe ApplicationSettingsHelper do end end - describe '.self_monitoring_project_data' do - context 'when self-monitoring project does not exist' do - it 'returns create_self_monitoring_project_path' do - expect(helper.self_monitoring_project_data).to include( - 'create_self_monitoring_project_path' => - create_self_monitoring_project_admin_application_settings_path - ) - end - - it 'returns status_create_self_monitoring_project_path' do - expect(helper.self_monitoring_project_data).to include( - 'status_create_self_monitoring_project_path' => - status_create_self_monitoring_project_admin_application_settings_path - ) - end - - it 'returns delete_self_monitoring_project_path' do - expect(helper.self_monitoring_project_data).to include( - 'delete_self_monitoring_project_path' => - delete_self_monitoring_project_admin_application_settings_path - ) - end - - it 'returns status_delete_self_monitoring_project_path' do - expect(helper.self_monitoring_project_data).to include( - 'status_delete_self_monitoring_project_path' => - status_delete_self_monitoring_project_admin_application_settings_path - ) - end - - it 'returns self_monitoring_project_exists false' do - expect(helper.self_monitoring_project_data).to include( - 'self_monitoring_project_exists' => "false" - ) - end - - it 'returns nil for project full_path' do - expect(helper.self_monitoring_project_data).to include( - 'self_monitoring_project_full_path' => nil - ) - end - end - - context 'when self-monitoring project exists' do - let(:project) { build(:project) } - - before do - stub_application_setting(self_monitoring_project: project) - end - - it 'returns self_monitoring_project_exists true' do - expect(helper.self_monitoring_project_data).to include( - 'self_monitoring_project_exists' => "true" - ) - end - - it 'returns project full_path' do - expect(helper.self_monitoring_project_data).to include( - 'self_monitoring_project_full_path' => project.full_path - ) - end - end - end - describe '#storage_weights' do let(:application_setting) { build(:application_setting) } diff --git a/spec/helpers/artifacts_helper_spec.rb b/spec/helpers/artifacts_helper_spec.rb index cf48f0ecc39..7c577cbf11c 100644 --- a/spec/helpers/artifacts_helper_spec.rb +++ b/spec/helpers/artifacts_helper_spec.rb @@ -17,6 +17,7 @@ RSpec.describe ArtifactsHelper, feature_category: :build_artifacts do it 'returns expected data' do expect(subject).to include({ project_path: project.full_path, + project_id: project.id, artifacts_management_feedback_image_path: match_asset_path('illustrations/chat-bubble-sm.svg') }) end diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb index bf23c74c0f0..dd0d6d1246f 100644 --- a/spec/helpers/avatars_helper_spec.rb +++ b/spec/helpers/avatars_helper_spec.rb @@ -102,7 +102,7 @@ RSpec.describe AvatarsHelper, feature_category: :source_code_management do end describe '#avatar_icon_for_email', :clean_gitlab_redis_cache do - let(:user) { create(:user, :public_email, avatar: File.open(uploaded_image_temp_path)) } + let(:user) { create(:user, :public_email, :commit_email, avatar: File.open(uploaded_image_temp_path)) } subject { helper.avatar_icon_for_email(user.email).to_s } @@ -131,13 +131,22 @@ RSpec.describe AvatarsHelper, feature_category: :source_code_management do end context 'without an email passed' do - it 'calls gravatar_icon' do - expect(helper).to receive(:gravatar_icon).with(nil, 20, 2) - expect(User).not_to receive(:find_by_any_email) + it 'returns the default avatar' do + expect(helper).to receive(:default_avatar) + expect(User).not_to receive(:with_public_email) helper.avatar_icon_for_email(nil, 20, 2) end end + + context 'with a blank email address' do + it 'returns the default avatar' do + expect(helper).to receive(:default_avatar) + expect(User).not_to receive(:with_public_email) + + helper.avatar_icon_for_email('', 20, 2) + end + end end end @@ -305,22 +314,26 @@ RSpec.describe AvatarsHelper, feature_category: :source_code_management do subject { helper.user_avatar_without_link(options) } it 'displays user avatar' do - is_expected.to eq tag.img(alt: "#{user.name}'s avatar", - src: avatar_icon_for_user(user, 16), - data: { container: 'body' }, - class: 'avatar s16 has-tooltip', - title: user.name) + is_expected.to eq tag.img( + alt: "#{user.name}'s avatar", + src: avatar_icon_for_user(user, 16), + data: { container: 'body' }, + class: 'avatar s16 has-tooltip', + title: user.name + ) end context 'with css_class parameter' do let(:options) { { user: user, css_class: '.cat-pics' } } it 'uses provided css_class' do - is_expected.to eq tag.img(alt: "#{user.name}'s avatar", - src: avatar_icon_for_user(user, 16), - data: { container: 'body' }, - class: "avatar s16 #{options[:css_class]} has-tooltip", - title: user.name) + is_expected.to eq tag.img( + alt: "#{user.name}'s avatar", + src: avatar_icon_for_user(user, 16), + data: { container: 'body' }, + class: "avatar s16 #{options[:css_class]} has-tooltip", + title: user.name + ) end end @@ -328,11 +341,13 @@ RSpec.describe AvatarsHelper, feature_category: :source_code_management do let(:options) { { user: user, size: 99 } } it 'uses provided size' do - is_expected.to eq tag.img(alt: "#{user.name}'s avatar", - src: avatar_icon_for_user(user, options[:size]), - data: { container: 'body' }, - class: "avatar s#{options[:size]} has-tooltip", - title: user.name) + is_expected.to eq tag.img( + alt: "#{user.name}'s avatar", + src: avatar_icon_for_user(user, options[:size]), + data: { container: 'body' }, + class: "avatar s#{options[:size]} has-tooltip", + title: user.name + ) end end @@ -340,11 +355,13 @@ RSpec.describe AvatarsHelper, feature_category: :source_code_management do let(:options) { { user: user, url: '/over/the/rainbow.png' } } it 'uses provided url' do - is_expected.to eq tag.img(alt: "#{user.name}'s avatar", - src: options[:url], - data: { container: 'body' }, - class: "avatar s16 has-tooltip", - title: user.name) + is_expected.to eq tag.img( + alt: "#{user.name}'s avatar", + src: options[:url], + data: { container: 'body' }, + class: "avatar s16 has-tooltip", + title: user.name + ) end end @@ -352,11 +369,13 @@ RSpec.describe AvatarsHelper, feature_category: :source_code_management do let(:options) { { user: user, lazy: true } } it 'adds `lazy` class to class list, sets `data-src` with avatar URL and `src` with placeholder image' do - is_expected.to eq tag.img(alt: "#{user.name}'s avatar", - src: LazyImageTagHelper.placeholder_image, - data: { container: 'body', src: avatar_icon_for_user(user, 16) }, - class: "avatar s16 has-tooltip lazy", - title: user.name) + is_expected.to eq tag.img( + alt: "#{user.name}'s avatar", + src: LazyImageTagHelper.placeholder_image, + data: { container: 'body', src: avatar_icon_for_user(user, 16) }, + class: "avatar s16 has-tooltip lazy", + title: user.name + ) end end @@ -365,11 +384,13 @@ RSpec.describe AvatarsHelper, feature_category: :source_code_management do let(:options) { { user: user, has_tooltip: true } } it 'adds has-tooltip' do - is_expected.to eq tag.img(alt: "#{user.name}'s avatar", - src: avatar_icon_for_user(user, 16), - data: { container: 'body' }, - class: "avatar s16 has-tooltip", - title: user.name) + is_expected.to eq tag.img( + alt: "#{user.name}'s avatar", + src: avatar_icon_for_user(user, 16), + data: { container: 'body' }, + class: "avatar s16 has-tooltip", + title: user.name + ) end end @@ -377,10 +398,12 @@ RSpec.describe AvatarsHelper, feature_category: :source_code_management do let(:options) { { user: user, has_tooltip: false } } it 'does not add has-tooltip or data container' do - is_expected.to eq tag.img(alt: "#{user.name}'s avatar", - src: avatar_icon_for_user(user, 16), - class: "avatar s16", - title: user.name) + is_expected.to eq tag.img( + alt: "#{user.name}'s avatar", + src: avatar_icon_for_user(user, 16), + class: "avatar s16", + title: user.name + ) end end end @@ -392,20 +415,24 @@ RSpec.describe AvatarsHelper, feature_category: :source_code_management do let(:options) { { user: user, user_name: 'Tinky Winky' } } it 'prefers user parameter' do - is_expected.to eq tag.img(alt: "#{user.name}'s avatar", - src: avatar_icon_for_user(user, 16), - data: { container: 'body' }, - class: "avatar s16 has-tooltip", - title: user.name) + is_expected.to eq tag.img( + alt: "#{user.name}'s avatar", + src: avatar_icon_for_user(user, 16), + data: { container: 'body' }, + class: "avatar s16 has-tooltip", + title: user.name + ) end end it 'uses user_name and user_email parameter if user is not present' do - is_expected.to eq tag.img(alt: "#{options[:user_name]}'s avatar", - src: helper.avatar_icon_for_email(options[:user_email], 16), - data: { container: 'body' }, - class: "avatar s16 has-tooltip", - title: options[:user_name]) + is_expected.to eq tag.img( + alt: "#{options[:user_name]}'s avatar", + src: helper.avatar_icon_for_email(options[:user_email], 16), + data: { container: 'body' }, + class: "avatar s16 has-tooltip", + title: options[:user_name] + ) end end @@ -416,11 +443,13 @@ RSpec.describe AvatarsHelper, feature_category: :source_code_management do let(:options) { { user: user_with_avatar, only_path: false } } it 'will return avatar with a full path' do - is_expected.to eq tag.img(alt: "#{user_with_avatar.name}'s avatar", - src: avatar_icon_for_user(user_with_avatar, 16, only_path: false), - data: { container: 'body' }, - class: "avatar s16 has-tooltip", - title: user_with_avatar.name) + is_expected.to eq tag.img( + alt: "#{user_with_avatar.name}'s avatar", + src: avatar_icon_for_user(user_with_avatar, 16, only_path: false), + data: { container: 'body' }, + class: "avatar s16 has-tooltip", + title: user_with_avatar.name + ) end end @@ -428,11 +457,13 @@ RSpec.describe AvatarsHelper, feature_category: :source_code_management do let(:options) { { user_email: user_with_avatar.email, user_name: user_with_avatar.username, only_path: false } } it 'will return avatar with a full path' do - is_expected.to eq tag.img(alt: "#{user_with_avatar.username}'s avatar", - src: helper.avatar_icon_for_email(user_with_avatar.email, 16, only_path: false), - data: { container: 'body' }, - class: "avatar s16 has-tooltip", - title: user_with_avatar.username) + is_expected.to eq tag.img( + alt: "#{user_with_avatar.username}'s avatar", + src: helper.avatar_icon_for_email(user_with_avatar.email, 16, only_path: false), + data: { container: 'body' }, + class: "avatar s16 has-tooltip", + title: user_with_avatar.username + ) end end end @@ -455,11 +486,13 @@ RSpec.describe AvatarsHelper, feature_category: :source_code_management do let(:resource) { user.namespace } it 'displays user avatar' do - is_expected.to eq tag.img(alt: "#{user.name}'s avatar", - src: avatar_icon_for_user(user, 32), - data: { container: 'body' }, - class: 'avatar s32 has-tooltip', - title: user.name) + is_expected.to eq tag.img( + alt: "#{user.name}'s avatar", + src: avatar_icon_for_user(user, 32), + data: { container: 'body' }, + class: 'avatar s32 has-tooltip', + title: user.name + ) end end diff --git a/spec/helpers/blame_helper_spec.rb b/spec/helpers/blame_helper_spec.rb index d305c4c595e..30670064d93 100644 --- a/spec/helpers/blame_helper_spec.rb +++ b/spec/helpers/blame_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BlameHelper do +RSpec.describe BlameHelper, feature_category: :source_code_management do describe '#get_age_map_start_date' do let(:dates) do [Time.zone.local(2014, 3, 17, 0, 0, 0), @@ -67,4 +67,14 @@ RSpec.describe BlameHelper do end end end + + describe '#entire_blame_path' do + subject { helper.entire_blame_path(id, project) } + + let_it_be(:project) { build_stubbed(:project) } + + let(:id) { 'main/README.md' } + + it { is_expected.to eq "/#{project.full_path}/-/blame/#{id}/streaming" } + end end diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index dac0d3fe182..1fd953d52d8 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe BlobHelper do include TreeHelper + include FakeBlobHelpers describe "#sanitize_svg_data" do let(:input_svg_path) { File.join(Rails.root, 'spec', 'fixtures', 'unsanitized.svg') } @@ -57,8 +58,6 @@ RSpec.describe BlobHelper do end describe "#relative_raw_path" do - include FakeBlobHelpers - let_it_be(:project) { create(:project) } before do @@ -82,8 +81,6 @@ RSpec.describe BlobHelper do end context 'viewer related' do - include FakeBlobHelpers - let_it_be(:project) { create(:project, lfs_enabled: true) } before do @@ -526,4 +523,25 @@ RSpec.describe BlobHelper do it { is_expected.to be_truthy } end end + + describe '#vue_blob_app_data' do + let(:blob) { fake_blob(path: 'file.md', size: 2.megabytes) } + let(:project) { build_stubbed(:project) } + let(:user) { build_stubbed(:user) } + let(:ref) { 'main' } + + it 'returns data related to blob app' do + allow(helper).to receive(:current_user).and_return(user) + assign(:ref, ref) + + expect(helper.vue_blob_app_data(project, blob, ref)).to include({ + blob_path: blob.path, + project_path: project.full_path, + resource_id: project.to_global_id, + user_id: user.to_global_id, + target_branch: ref, + original_branch: ref + }) + end + end end diff --git a/spec/helpers/broadcast_messages_helper_spec.rb b/spec/helpers/broadcast_messages_helper_spec.rb index e0bdb09f257..5d6d404d24d 100644 --- a/spec/helpers/broadcast_messages_helper_spec.rb +++ b/spec/helpers/broadcast_messages_helper_spec.rb @@ -12,11 +12,8 @@ RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do end shared_examples 'returns role-targeted broadcast message when in project, group, or sub-group URL' do - let(:feature_flag_state) { true } - before do - stub_feature_flags(role_targeted_broadcast_messages: feature_flag_state) - allow(helper).to receive(:cookies) { {} } + allow(helper).to receive(:cookies).and_return({}) end context 'when in a project page' do @@ -30,12 +27,6 @@ RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do end it { is_expected.to eq message } - - context 'when feature flag is disabled' do - let(:feature_flag_state) { false } - - it { is_expected.to be_nil } - end end context 'when in a group page' do @@ -49,22 +40,10 @@ RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do end it { is_expected.to eq message } - - context 'when feature flag is disabled' do - let(:feature_flag_state) { false } - - it { is_expected.to be_nil } - end end context 'when not in a project, group, or sub-group page' do it { is_expected.to be_nil } - - context 'when feature flag is disabled' do - let(:feature_flag_state) { false } - - it { is_expected.to be_nil } - end end end @@ -72,7 +51,10 @@ RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do subject { helper.current_broadcast_notification_message } context 'with available broadcast notification messages' do - let!(:broadcast_message_1) { create(:broadcast_message, broadcast_type: 'notification', starts_at: Time.now - 1.day) } + let!(:broadcast_message_1) do + create(:broadcast_message, broadcast_type: 'notification', starts_at: Time.now - 1.day) + end + let!(:broadcast_message_2) { create(:broadcast_message, broadcast_type: 'notification', starts_at: Time.now) } it { is_expected.to eq broadcast_message_2 } @@ -91,7 +73,13 @@ RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do end describe 'user access level targeted messages' do - let_it_be(:message) { create(:broadcast_message, broadcast_type: 'notification', starts_at: Time.now, target_access_levels: [Gitlab::Access::DEVELOPER]) } + let_it_be(:message) do + create(:broadcast_message, + broadcast_type: 'notification', + starts_at: Time.now, + target_access_levels: [Gitlab::Access::DEVELOPER] + ) + end include_examples 'returns role-targeted broadcast message when in project, group, or sub-group URL' end @@ -99,7 +87,13 @@ RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do describe '#current_broadcast_banner_messages' do describe 'user access level targeted messages' do - let_it_be(:message) { create(:broadcast_message, broadcast_type: 'banner', starts_at: Time.now, target_access_levels: [Gitlab::Access::DEVELOPER]) } + let_it_be(:message) do + create(:broadcast_message, + broadcast_type: 'banner', + starts_at: Time.now, + target_access_levels: [Gitlab::Access::DEVELOPER] + ) + end subject { helper.current_broadcast_banner_messages.first } @@ -147,7 +141,20 @@ RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do subject(:single_broadcast_message) { Gitlab::Json.parse(admin_broadcast_messages_data([message])).first } it 'returns the expected messages data attributes' do - keys = %w[id status preview starts_at ends_at target_roles target_path type edit_path delete_path] + keys = %w[ + id + status + message + theme + broadcast_type + dismissable + starts_at + ends_at + target_roles + target_path + type edit_path + delete_path + ] expect(single_broadcast_message.keys).to match(keys) end @@ -157,4 +164,24 @@ RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do expect(single_broadcast_message['ends_at']).to eq('2020-01-02T00:00:00Z') end end + + describe '#broadcast_message_data' do + let(:starts_at) { 1.day.ago } + let(:ends_at) { 1.day.from_now } + let(:message) { build(:broadcast_message, id: non_existing_record_id, starts_at: starts_at, ends_at: ends_at) } + + it 'returns the expected message data attributes' do + keys = [ + :id, :message, :broadcast_type, :theme, :dismissable, :target_access_levels, :messages_path, + :preview_path, :target_path, :starts_at, :ends_at, :target_access_level_options + ] + + expect(broadcast_message_data(message).keys).to match(keys) + end + + it 'has the correct iso formatted date', time_travel_to: '2020-01-01 00:00:00 +0000' do + expect(broadcast_message_data(message)[:starts_at]).to eq('2019-12-31T00:00:00Z') + expect(broadcast_message_data(message)[:ends_at]).to eq('2020-01-02T00:00:00Z') + end + end end diff --git a/spec/helpers/ci/builds_helper_spec.rb b/spec/helpers/ci/builds_helper_spec.rb index c215d7b4a78..eabd40f3dd4 100644 --- a/spec/helpers/ci/builds_helper_spec.rb +++ b/spec/helpers/ci/builds_helper_spec.rb @@ -3,51 +3,6 @@ require 'spec_helper' RSpec.describe Ci::BuildsHelper do - describe '#build_summary' do - subject { helper.build_summary(build, skip: skip) } - - context 'when build has no trace' do - let(:build) { instance_double(Ci::Build, has_trace?: false) } - - context 'when skip is false' do - let(:skip) { false } - - it 'returns no job log' do - expect(subject).to eq('No job log') - end - end - - context 'when skip is true' do - let(:skip) { true } - - it 'returns no job log' do - expect(subject).to eq('No job log') - end - end - end - - context 'when build has trace' do - let(:build) { create(:ci_build, :trace_live) } - - context 'when skip is true' do - let(:skip) { true } - - it 'returns link to logs' do - expect(subject).to include('View job log') - expect(subject).to include(pipeline_job_url(build.pipeline, build)) - end - end - - context 'when skip is false' do - let(:skip) { false } - - it 'returns log lines' do - expect(subject).to include(build.trace.html(last_lines: 10).html_safe) - end - end - end - end - describe '#sidebar_build_class' do using RSpec::Parameterized::TableSyntax @@ -97,20 +52,6 @@ RSpec.describe Ci::BuildsHelper do end end - describe '#prepare_failed_jobs_summary_data' do - let(:failed_build) { create(:ci_build, :failed, :trace_live) } - - subject { helper.prepare_failed_jobs_summary_data([failed_build]) } - - it 'returns array of failed jobs with id, failure and failure summary' do - expect(subject).to eq([{ - id: failed_build.id, - failure: failed_build.present.callout_failure_message, - failure_summary: helper.build_summary(failed_build) - }].to_json) - end - end - def assign_project build(:project).tap do |project| assign(:project, project) diff --git a/spec/helpers/ci/catalog/resources_helper_spec.rb b/spec/helpers/ci/catalog/resources_helper_spec.rb new file mode 100644 index 00000000000..e873b9379fe --- /dev/null +++ b/spec/helpers/ci/catalog/resources_helper_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::Catalog::ResourcesHelper, feature_category: :pipeline_composition do + include Devise::Test::ControllerHelpers + + let_it_be(:project) { build(:project) } + + describe '#can_view_namespace_catalog?' do + subject { helper.can_view_namespace_catalog?(project) } + + before do + stub_licensed_features(ci_namespace_catalog: false) + end + + it 'user cannot view the Catalog in CE regardless of permissions' do + expect(subject).to be false + end + end + + describe '#js_ci_catalog_data' do + let(:project) { build(:project, :repository) } + + let(:default_helper_data) do + {} + end + + subject(:catalog_data) { helper.js_ci_catalog_data(project) } + + it 'returns catalog data' do + expect(catalog_data).to eq(default_helper_data) + end + end +end diff --git a/spec/helpers/ci/jobs_helper_spec.rb b/spec/helpers/ci/jobs_helper_spec.rb index 489d9d3fcee..a9ab4ab3b67 100644 --- a/spec/helpers/ci/jobs_helper_spec.rb +++ b/spec/helpers/ci/jobs_helper_spec.rb @@ -3,24 +3,49 @@ require 'spec_helper' RSpec.describe Ci::JobsHelper do - describe 'jobs data' do - let(:project) { create(:project, :repository) } - let(:bridge) { create(:ci_bridge) } - - subject(:bridge_data) { helper.bridge_data(bridge, project) } + describe 'job helper functions' do + let_it_be(:project) { create(:project, :repository) } + let_it_be(:job) { create(:ci_build, project: project) } before do - allow(helper) - .to receive(:image_path) - .and_return('/path/to/illustration') + helper.instance_variable_set(:@project, project) + helper.instance_variable_set(:@build, job) + end + + it 'returns jobs data' do + expect(helper.jobs_data).to include({ + "endpoint" => "/#{project.full_path}/-/jobs/#{job.id}.json", + "project_path" => project.full_path, + "artifact_help_url" => "/help/user/gitlab_com/index.md#gitlab-cicd", + "deployment_help_url" => "/help/user/project/clusters/deploy_to_cluster.md#troubleshooting", + "runner_settings_url" => "/#{project.full_path}/-/runners#js-runners-settings", + "page_path" => "/#{project.full_path}/-/jobs/#{job.id}", + "build_status" => "pending", + "build_stage" => "test", + "log_state" => "", + "build_options" => { + build_stage: "test", + build_status: "pending", + log_state: "", + page_path: "/#{project.full_path}/-/jobs/#{job.id}" + }, + "retry_outdated_job_docs_url" => "/help/ci/pipelines/settings#retry-outdated-jobs" + }) end - it 'returns bridge data' do - expect(bridge_data).to eq({ - "build_id" => bridge.id, - "empty-state-illustration-path" => '/path/to/illustration', - "pipeline_iid" => bridge.pipeline.iid, - "project_full_path" => project.full_path + it 'returns job statuses' do + expect(helper.job_statuses).to eq({ + "canceled" => "CANCELED", + "created" => "CREATED", + "failed" => "FAILED", + "manual" => "MANUAL", + "pending" => "PENDING", + "preparing" => "PREPARING", + "running" => "RUNNING", + "scheduled" => "SCHEDULED", + "skipped" => "SKIPPED", + "success" => "SUCCESS", + "waiting_for_resource" => "WAITING_FOR_RESOURCE" }) end end diff --git a/spec/helpers/ci/pipeline_editor_helper_spec.rb b/spec/helpers/ci/pipeline_editor_helper_spec.rb index c9aac63a883..b45882d9888 100644 --- a/spec/helpers/ci/pipeline_editor_helper_spec.rb +++ b/spec/helpers/ci/pipeline_editor_helper_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe Ci::PipelineEditorHelper do let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } describe 'can_view_pipeline_editor?' do subject { helper.can_view_pipeline_editor?(project) } @@ -29,12 +30,12 @@ RSpec.describe Ci::PipelineEditorHelper do "ci-examples-help-page-path" => help_page_path('ci/examples/index'), "ci-help-page-path" => help_page_path('ci/index'), "ci-lint-path" => project_ci_lint_path(project), + "ci-troubleshooting-path" => help_page_path('ci/troubleshooting', anchor: 'common-cicd-issues'), "default-branch" => project.default_branch_or_main, "empty-state-illustration-path" => 'illustrations/empty.svg', "initial-branch-name" => nil, "includes-help-page-path" => help_page_path('ci/yaml/includes'), "lint-help-page-path" => help_page_path('ci/lint', anchor: 'check-cicd-syntax'), - "lint-unavailable-help-page-path" => help_page_path('ci/pipeline_editor/index', anchor: 'configuration-validation-currently-not-available-message'), "needs-help-page-path" => help_page_path('ci/yaml/index', anchor: 'needs'), "new-merge-request-path" => '/mock/project/-/merge_requests/new', "pipeline-page-path" => project_pipelines_path(project), @@ -62,6 +63,10 @@ RSpec.describe Ci::PipelineEditorHelper do .to receive(:image_path) .with('illustrations/project-run-CICD-pipelines-sm.svg') .and_return('illustrations/validate.svg') + + allow(helper) + .to receive(:current_user) + .and_return(user) end subject(:pipeline_editor_data) { helper.js_pipeline_editor_data(project) } diff --git a/spec/helpers/ci/pipelines_helper_spec.rb b/spec/helpers/ci/pipelines_helper_spec.rb index 19946afb1a4..6463da7c53f 100644 --- a/spec/helpers/ci/pipelines_helper_spec.rb +++ b/spec/helpers/ci/pipelines_helper_spec.rb @@ -121,35 +121,7 @@ RSpec.describe Ci::PipelinesHelper do :has_gitlab_ci, :pipeline_editor_path, :suggested_ci_templates, - :ci_runner_settings_path]) - end - - describe 'the `any_runners_available` attribute' do - subject { data[:any_runners_available] } - - context 'when the `runners_availability_section` experiment variant is control' do - before do - stub_experiments(runners_availability_section: :control) - end - - it { is_expected.to be_nil } - end - - context 'when the `runners_availability_section` experiment variant is candidate' do - before do - stub_experiments(runners_availability_section: :candidate) - end - - context 'when there are no runners' do - it { is_expected.to eq('false') } - end - - context 'when there are runners' do - let!(:runner) { create(:ci_runner, :project, projects: [project]) } - - it { is_expected.to eq('true') } - end - end + :full_path]) end describe 'when the project is eligible for the `ios_specific_templates` experiment' do @@ -192,11 +164,7 @@ RSpec.describe Ci::PipelinesHelper do end end - describe 'the `ios_runners_available` attribute' do - before do - allow(Gitlab).to receive(:com?).and_return(true) - end - + describe 'the `ios_runners_available` attribute', :saas do subject { data[:ios_runners_available] } context 'when the `ios_specific_templates` experiment variant is control' do diff --git a/spec/helpers/ci/runners_helper_spec.rb b/spec/helpers/ci/runners_helper_spec.rb index 6d14abd6574..c170d7fae67 100644 --- a/spec/helpers/ci/runners_helper_spec.rb +++ b/spec/helpers/ci/runners_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::RunnersHelper do +RSpec.describe Ci::RunnersHelper, feature_category: :runner_fleet do let_it_be(:user) { create(:user) } before do @@ -38,6 +38,14 @@ RSpec.describe Ci::RunnersHelper do end end + describe '#runner_short_name' do + it 'shows runner short name' do + runner = build_stubbed(:ci_runner, id: non_existing_record_id) + + expect(helper.runner_short_name(runner)).to eq("##{runner.id} (#{runner.short_sha})") + end + end + describe '#runner_contacted_at' do let(:contacted_at_stored) { 1.hour.ago.change(usec: 0) } let(:contacted_at_cached) { 1.second.ago.change(usec: 0) } @@ -77,7 +85,7 @@ RSpec.describe Ci::RunnersHelper do describe '#admin_runners_data_attributes' do let_it_be(:admin) { create(:user, :admin) } let_it_be(:instance_runner) { create(:ci_runner, :instance) } - let_it_be(:project_runner) { create(:ci_runner, :project ) } + let_it_be(:project_runner) { create(:ci_runner, :project) } before do allow(helper).to receive(:current_user).and_return(admin) @@ -88,9 +96,7 @@ RSpec.describe Ci::RunnersHelper do runner_install_help_page: 'https://docs.gitlab.com/runner/install/', registration_token: Gitlab::CurrentSettings.runners_registration_token, online_contact_timeout_secs: 7200, - stale_timeout_secs: 7889238, - empty_state_svg_path: start_with('/assets/illustrations/pipelines_empty'), - empty_state_filtered_svg_path: start_with('/assets/illustrations/magnifying-glass') + stale_timeout_secs: 7889238 ) end end @@ -98,6 +104,8 @@ RSpec.describe Ci::RunnersHelper do describe '#group_shared_runners_settings_data' do let_it_be(:parent) { create(:group) } let_it_be(:group) { create(:group, parent: parent, shared_runners_enabled: false) } + let_it_be(:group_with_project) { create(:group, parent: parent) } + let_it_be(:project) { create(:project, group: group_with_project) } let(:runner_constants) do { @@ -110,6 +118,8 @@ RSpec.describe Ci::RunnersHelper do it 'returns group data for top level group' do result = { group_id: parent.id, + group_name: parent.name, + group_is_empty: 'false', shared_runners_setting: Namespace::SR_ENABLED, parent_shared_runners_setting: nil }.merge(runner_constants) @@ -120,12 +130,26 @@ RSpec.describe Ci::RunnersHelper do it 'returns group data for child group' do result = { group_id: group.id, + group_name: group.name, + group_is_empty: 'true', shared_runners_setting: Namespace::SR_DISABLED_AND_UNOVERRIDABLE, parent_shared_runners_setting: Namespace::SR_ENABLED }.merge(runner_constants) expect(helper.group_shared_runners_settings_data(group)).to eq result end + + it 'returns group data for child group with project' do + result = { + group_id: group_with_project.id, + group_name: group_with_project.name, + group_is_empty: 'false', + shared_runners_setting: Namespace::SR_ENABLED, + parent_shared_runners_setting: Namespace::SR_ENABLED + }.merge(runner_constants) + + expect(helper.group_shared_runners_settings_data(group_with_project)).to eq result + end end describe '#group_runners_data_attributes' do @@ -142,9 +166,7 @@ RSpec.describe Ci::RunnersHelper do group_full_path: group.full_path, runner_install_help_page: 'https://docs.gitlab.com/runner/install/', online_contact_timeout_secs: 7200, - stale_timeout_secs: 7889238, - empty_state_svg_path: start_with('/assets/illustrations/pipelines_empty'), - empty_state_filtered_svg_path: start_with('/assets/illustrations/magnifying-glass') + stale_timeout_secs: 7889238 ) end end diff --git a/spec/helpers/ci/variables_helper_spec.rb b/spec/helpers/ci/variables_helper_spec.rb index d032e7f9087..9c3236ace72 100644 --- a/spec/helpers/ci/variables_helper_spec.rb +++ b/spec/helpers/ci/variables_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::VariablesHelper, feature_category: :pipeline_authoring do +RSpec.describe Ci::VariablesHelper, feature_category: :secrets_management do describe '#ci_variable_maskable_raw_regex' do it 'converts to a javascript regex' do expect(helper.ci_variable_maskable_raw_regex).to eq("^\\S{8,}$") diff --git a/spec/helpers/clusters_helper_spec.rb b/spec/helpers/clusters_helper_spec.rb index 9a3cd5fd18d..41a8dea7f5a 100644 --- a/spec/helpers/clusters_helper_spec.rb +++ b/spec/helpers/clusters_helper_spec.rb @@ -166,6 +166,90 @@ RSpec.describe ClustersHelper do end end + describe '#render_cluster_info_tab_content' do + subject { helper.render_cluster_info_tab_content(tab, expanded) } + + let(:expanded) { true } + + context 'environments' do + let(:tab) { 'environments' } + + it 'renders environemtns tab' do + expect(helper).to receive(:render_if_exists).with('clusters/clusters/environments') + subject + end + end + + context 'health' do + let(:tab) { 'health' } + + it 'renders details tab' do + expect(helper).to receive(:render).with('details', { expanded: expanded }) + subject + end + end + + context 'apps' do + let(:tab) { 'apps' } + + it 'renders apps tab' do + expect(helper).to receive(:render).with('applications') + subject + end + end + + context 'integrations ' do + let(:tab) { 'integrations' } + + it 'renders details tab' do + expect(helper).to receive(:render).with('details', { expanded: expanded }) + subject + end + end + + context 'settings' do + let(:tab) { 'settings' } + + it 'renders settings tab' do + expect(helper).to receive(:render).with('advanced_settings_container') + subject + end + end + + context 'details ' do + let(:tab) { 'details' } + + it 'renders details tab' do + expect(helper).to receive(:render).with('details', { expanded: expanded }) + subject + end + end + + context 'when remove_monitor_metrics FF is disabled' do + before do + stub_feature_flags(remove_monitor_metrics: false) + end + + context 'health' do + let(:tab) { 'health' } + + it 'renders health tab' do + expect(helper).to receive(:render_if_exists).with('clusters/clusters/health') + subject + end + end + + context 'integrations ' do + let(:tab) { 'integrations' } + + it 'renders integrations tab' do + expect(helper).to receive(:render).with('integrations') + subject + end + end + end + end + describe '#cluster_type_label' do subject { helper.cluster_type_label(cluster_type) } diff --git a/spec/helpers/commits_helper_spec.rb b/spec/helpers/commits_helper_spec.rb index 27738f73ea5..2d06f42dee4 100644 --- a/spec/helpers/commits_helper_spec.rb +++ b/spec/helpers/commits_helper_spec.rb @@ -325,21 +325,20 @@ RSpec.describe CommitsHelper do assign(:path, current_path) end - it { is_expected.to be_an(Array) } - it { is_expected.to include(commit) } - it { is_expected.to include(commit.author) } - it { is_expected.to include(ref) } - specify do - is_expected.to include( + expect(subject).to eq([ + commit, + commit.author, + ref, { merge_request: merge_request.cache_key, pipeline_status: pipeline.cache_key, xhr: true, controller: "commits", - path: current_path + path: current_path, + referenced_by: helper.tag_checksum(commit.referenced_by) } - ) + ]) end describe "final cache key output" do diff --git a/spec/helpers/device_registration_helper_spec.rb b/spec/helpers/device_registration_helper_spec.rb new file mode 100644 index 00000000000..7556d037b3d --- /dev/null +++ b/spec/helpers/device_registration_helper_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe DeviceRegistrationHelper, feature_category: :system_access do + describe "#device_registration_data" do + it "returns a hash with device registration properties without initial error" do + device_registration_data = helper.device_registration_data( + current_password_required: false, + target_path: "/my/path", + webauthn_error: nil + ) + + expect(device_registration_data).to eq( + { + initial_error: nil, + target_path: "/my/path", + password_required: "false" + }) + end + + it "returns a hash with device registration properties with initial error" do + device_registration_data = helper.device_registration_data( + current_password_required: true, + target_path: "/my/path", + webauthn_error: { message: "my error" } + ) + + expect(device_registration_data).to eq( + { + initial_error: "my error", + target_path: "/my/path", + password_required: "true" + }) + end + end +end diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index a46f8c13f00..2318bbf861a 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -47,6 +47,12 @@ RSpec.describe DiffHelper do end describe 'diff_options' do + let(:large_notebooks_enabled) { false } + + before do + stub_feature_flags(large_ipynb_diffs: large_notebooks_enabled) + end + it 'returns no collapse false' do expect(diff_options).to include(expanded: false) end @@ -56,21 +62,48 @@ RSpec.describe DiffHelper do expect(diff_options).to include(expanded: true) end - it 'returns no collapse true if action name diff_for_path' do - allow(controller).to receive(:action_name) { 'diff_for_path' } - expect(diff_options).to include(expanded: true) - end + context 'when action name is diff_for_path' do + before do + allow(controller).to receive(:action_name) { 'diff_for_path' } + end - it 'returns paths if action name diff_for_path and param old path' do - allow(controller).to receive(:params) { { old_path: 'lib/wadus.rb' } } - allow(controller).to receive(:action_name) { 'diff_for_path' } - expect(diff_options[:paths]).to include('lib/wadus.rb') - end + it 'returns expanded true' do + expect(diff_options).to include(expanded: true) + end - it 'returns paths if action name diff_for_path and param new path' do - allow(controller).to receive(:params) { { new_path: 'lib/wadus.rb' } } - allow(controller).to receive(:action_name) { 'diff_for_path' } - expect(diff_options[:paths]).to include('lib/wadus.rb') + it 'returns paths if param old path' do + allow(controller).to receive(:params) { { old_path: 'lib/wadus.rb' } } + expect(diff_options[:paths]).to include('lib/wadus.rb') + end + + it 'returns paths if param new path' do + allow(controller).to receive(:params) { { new_path: 'lib/wadus.rb' } } + expect(diff_options[:paths]).to include('lib/wadus.rb') + end + + it 'does not set max_patch_bytes_for_file_extension' do + expect(diff_options[:max_patch_bytes_for_file_extension]).to be_nil + end + + context 'when file_identifier include .ipynb' do + before do + allow(controller).to receive(:params) { { file_identifier: 'something.ipynb' } } + end + + context 'when large_ipynb_diffs is disabled' do + it 'does not set max_patch_bytes_for_file_extension' do + expect(diff_options[:max_patch_bytes_for_file_extension]).to be_nil + end + end + + context 'when large_ipynb_diffs is enabled' do + let(:large_notebooks_enabled) { true } + + it 'sets max_patch_bytes_for_file_extension' do + expect(diff_options[:max_patch_bytes_for_file_extension]).to eq({ '.ipynb' => 1.megabyte }) + end + end + end end end diff --git a/spec/helpers/emoji_helper_spec.rb b/spec/helpers/emoji_helper_spec.rb index 6f4c962c0fb..e16c96c86ed 100644 --- a/spec/helpers/emoji_helper_spec.rb +++ b/spec/helpers/emoji_helper_spec.rb @@ -12,10 +12,12 @@ RSpec.describe EmojiHelper do subject { helper.emoji_icon(emoji_text, options) } it 'has no options' do - is_expected.to include(' project_metrics_dashboard_path(project, environment: environment) + ) + end + end end end diff --git a/spec/helpers/environments_helper_spec.rb b/spec/helpers/environments_helper_spec.rb index cf33f8a4939..0ebec3ed6d0 100644 --- a/spec/helpers/environments_helper_spec.rb +++ b/spec/helpers/environments_helper_spec.rb @@ -2,13 +2,15 @@ require 'spec_helper' -RSpec.describe EnvironmentsHelper do +RSpec.describe EnvironmentsHelper, feature_category: :environment_management do let_it_be(:user) { create(:user) } let_it_be(:project, reload: true) { create(:project, :repository) } let_it_be(:environment) { create(:environment, project: project) } - describe '#metrics_data' do + describe '#metrics_data', feature_category: :metrics do before do + stub_feature_flags(remove_monitor_metrics: false) + # This is so that this spec also passes in EE. allow(helper).to receive(:current_user).and_return(user) allow(helper).to receive(:can?).and_return(true) @@ -103,9 +105,19 @@ RSpec.describe EnvironmentsHelper do end end end + + context 'when metrics dashboard feature is unavailable' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it 'does not return data' do + expect(metrics_data).to be_empty + end + end end - describe '#custom_metrics_available?' do + describe '#custom_metrics_available?', feature_category: :metrics do subject { helper.custom_metrics_available?(project) } before do diff --git a/spec/helpers/explore_helper_spec.rb b/spec/helpers/explore_helper_spec.rb index 4ae1b738858..68c5289a85f 100644 --- a/spec/helpers/explore_helper_spec.rb +++ b/spec/helpers/explore_helper_spec.rb @@ -12,7 +12,7 @@ RSpec.describe ExploreHelper do describe '#explore_nav_links' do it 'has all the expected links by default' do - menu_items = [:projects, :groups, :snippets] + menu_items = [:projects, :groups, :topics, :snippets] expect(helper.explore_nav_links).to contain_exactly(*menu_items) end diff --git a/spec/helpers/feature_flags_helper_spec.rb b/spec/helpers/feature_flags_helper_spec.rb index 786454c6c4d..a5e7f8d273e 100644 --- a/spec/helpers/feature_flags_helper_spec.rb +++ b/spec/helpers/feature_flags_helper_spec.rb @@ -33,12 +33,14 @@ RSpec.describe FeatureFlagsHelper do subject { helper.edit_feature_flag_data } it 'contains all the data needed to edit feature flags' do - is_expected.to include(endpoint: "/#{project.full_path}/-/feature_flags/#{feature_flag.iid}", - project_id: project.id, - feature_flags_path: "/#{project.full_path}/-/feature_flags", - environments_endpoint: "/#{project.full_path}/-/environments/search.json", - strategy_type_docs_page_path: "/help/operations/feature_flags#feature-flag-strategies", - environments_scope_docs_path: "/help/ci/environments/index.md#limit-the-environment-scope-of-a-cicd-variable") + is_expected.to include( + endpoint: "/#{project.full_path}/-/feature_flags/#{feature_flag.iid}", + project_id: project.id, + feature_flags_path: "/#{project.full_path}/-/feature_flags", + environments_endpoint: "/#{project.full_path}/-/environments/search.json", + strategy_type_docs_page_path: "/help/operations/feature_flags#feature-flag-strategies", + environments_scope_docs_path: "/help/ci/environments/index.md#limit-the-environment-scope-of-a-cicd-variable" + ) end end end diff --git a/spec/helpers/groups/observability_helper_spec.rb b/spec/helpers/groups/observability_helper_spec.rb index ee33a853f9c..f0e6aa0998a 100644 --- a/spec/helpers/groups/observability_helper_spec.rb +++ b/spec/helpers/groups/observability_helper_spec.rb @@ -4,77 +4,46 @@ require "spec_helper" RSpec.describe Groups::ObservabilityHelper do let(:group) { build_stubbed(:group) } - let(:observability_url) { Gitlab::Observability.observability_url } describe '#observability_iframe_src' do - context 'if observability_path is missing from params' do - it 'returns the iframe src for action: dashboards' do - allow(helper).to receive(:params).and_return({ action: 'dashboards' }) - expect(helper.observability_iframe_src(group)).to eq("#{observability_url}/-/#{group.id}/") - end - - it 'returns the iframe src for action: manage' do - allow(helper).to receive(:params).and_return({ action: 'manage' }) - expect(helper.observability_iframe_src(group)).to eq("#{observability_url}/-/#{group.id}/dashboards") - end - - it 'returns the iframe src for action: explore' do - allow(helper).to receive(:params).and_return({ action: 'explore' }) - expect(helper.observability_iframe_src(group)).to eq("#{observability_url}/-/#{group.id}/explore") - end - - it 'returns the iframe src for action: datasources' do - allow(helper).to receive(:params).and_return({ action: 'datasources' }) - expect(helper.observability_iframe_src(group)).to eq("#{observability_url}/-/#{group.id}/datasources") - end + before do + allow(Gitlab::Observability).to receive(:build_full_url).and_return('full-url') end - context 'if observability_path exists in params' do - context 'if observability_path is valid' do - it 'returns the iframe src by injecting the observability path' do - allow(helper).to receive(:params).and_return({ action: '/explore', observability_path: '/foo?bar=foobar' }) - expect(helper.observability_iframe_src(group)).to eq("#{observability_url}/-/#{group.id}/foo?bar=foobar") - end - end - - context 'if observability_path is not valid' do - it 'returns the iframe src by injecting the sanitised observability path' do - allow(helper).to receive(:params).and_return({ - action: '/explore', - observability_path: - "/test?groupId=" - }) - expect(helper.observability_iframe_src(group)).to eq( - "#{observability_url}/-/#{group.id}/test?groupId=alert('attack!')" - ) - end - end + it 'returns the iframe src for action: dashboards' do + allow(helper).to receive(:params).and_return({ action: 'dashboards', observability_path: '/foo?bar=foobar' }) + expect(helper.observability_iframe_src(group)).to eq('full-url') + expect(Gitlab::Observability).to have_received(:build_full_url).with(group, '/foo?bar=foobar', '/') end - context 'when observability ui is standalone' do - before do - stub_env('STANDALONE_OBSERVABILITY_UI', 'true') - end + it 'returns the iframe src for action: manage' do + allow(helper).to receive(:params).and_return({ action: 'manage', observability_path: '/foo?bar=foobar' }) + expect(helper.observability_iframe_src(group)).to eq('full-url') + expect(Gitlab::Observability).to have_received(:build_full_url).with(group, '/foo?bar=foobar', '/dashboards') + end - it 'returns the iframe src without group.id for action: dashboards' do - allow(helper).to receive(:params).and_return({ action: 'dashboards' }) - expect(helper.observability_iframe_src(group)).to eq("#{observability_url}/") - end + it 'returns the iframe src for action: explore' do + allow(helper).to receive(:params).and_return({ action: 'explore', observability_path: '/foo?bar=foobar' }) + expect(helper.observability_iframe_src(group)).to eq('full-url') + expect(Gitlab::Observability).to have_received(:build_full_url).with(group, '/foo?bar=foobar', '/explore') + end - it 'returns the iframe src without group.id for action: manage' do - allow(helper).to receive(:params).and_return({ action: 'manage' }) - expect(helper.observability_iframe_src(group)).to eq("#{observability_url}/dashboards") - end + it 'returns the iframe src for action: datasources' do + allow(helper).to receive(:params).and_return({ action: 'datasources', observability_path: '/foo?bar=foobar' }) + expect(helper.observability_iframe_src(group)).to eq('full-url') + expect(Gitlab::Observability).to have_received(:build_full_url).with(group, '/foo?bar=foobar', '/datasources') + end - it 'returns the iframe src without group.id for action: explore' do - allow(helper).to receive(:params).and_return({ action: 'explore' }) - expect(helper.observability_iframe_src(group)).to eq("#{observability_url}/explore") - end + it 'returns the iframe src when action is not recognised' do + allow(helper).to receive(:params).and_return({ action: 'unrecognised', observability_path: '/foo?bar=foobar' }) + expect(helper.observability_iframe_src(group)).to eq('full-url') + expect(Gitlab::Observability).to have_received(:build_full_url).with(group, '/foo?bar=foobar', '/') + end - it 'returns the iframe src without group.id for action: datasources' do - allow(helper).to receive(:params).and_return({ action: 'datasources' }) - expect(helper.observability_iframe_src(group)).to eq("#{observability_url}/datasources") - end + it 'returns the iframe src when observability_path is missing' do + allow(helper).to receive(:params).and_return({ action: 'dashboards' }) + expect(helper.observability_iframe_src(group)).to eq('full-url') + expect(Gitlab::Observability).to have_received(:build_full_url).with(group, nil, '/') end end diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 8b4ac6a7cfd..f66f9a8a58e 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -436,7 +436,8 @@ RSpec.describe GroupsHelper do it 'returns expected hash' do expect(subgroup_creation_data(subgroup)).to eq({ import_existing_group_path: '/groups/new#import-group-pane', - parent_group_name: name + parent_group_name: name, + parent_group_url: group_url(group) }) end end @@ -445,7 +446,8 @@ RSpec.describe GroupsHelper do it 'returns expected hash' do expect(subgroup_creation_data(group)).to eq({ import_existing_group_path: '/groups/new#import-group-pane', - parent_group_name: nil + parent_group_name: nil, + parent_group_url: nil }) end end @@ -495,6 +497,7 @@ RSpec.describe GroupsHelper do new_project_path: including("/projects/new?namespace_id=#{group.id}"), new_subgroup_illustration: including('illustrations/subgroup-create-new-sm'), new_project_illustration: including('illustrations/project-create-new-sm'), + empty_projects_illustration: including('illustrations/empty-state/empty-projects-md'), empty_subgroup_illustration: including('illustrations/empty-state/empty-subgroup-md'), render_empty_state: 'true', can_create_subgroups: 'true', diff --git a/spec/helpers/ide_helper_spec.rb b/spec/helpers/ide_helper_spec.rb index e2ee4f33eee..922155abf65 100644 --- a/spec/helpers/ide_helper_spec.rb +++ b/spec/helpers/ide_helper_spec.rb @@ -6,117 +6,136 @@ RSpec.describe IdeHelper, feature_category: :web_ide do describe '#ide_data' do let_it_be(:project) { create(:project) } let_it_be(:user) { project.creator } + let_it_be(:fork_info) { { ide_path: '/test/ide/path' } } + + let_it_be(:params) do + { + branch: 'master', + path: 'foo/bar', + merge_request_id: '1' + } + end + + let(:base_data) do + { + 'use-new-web-ide' => 'false', + 'user-preferences-path' => profile_preferences_path, + 'sign-in-path' => 'test-sign-in-path', + 'project' => nil, + 'preview-markdown-path' => nil + } + end before do allow(helper).to receive(:current_user).and_return(user) allow(helper).to receive(:content_security_policy_nonce).and_return('test-csp-nonce') + allow(helper).to receive(:new_session_path).and_return('test-sign-in-path') end - context 'with vscode_web_ide=true and instance vars set' do - before do - stub_feature_flags(vscode_web_ide: true) - end + it 'returns hash' do + expect(helper.ide_data(project: nil, fork_info: fork_info, params: params)) + .to include(base_data) + end - it 'returns hash' do - expect(helper.ide_data(project: project, branch: 'master', path: 'foo/README.md', merge_request: '7', -fork_info: nil)) - .to match( - 'can-use-new-web-ide' => 'true', - 'use-new-web-ide' => 'true', - 'user-preferences-path' => profile_preferences_path, - 'new-web-ide-help-page-path' => - help_page_path('user/project/web_ide/index.md', anchor: 'vscode-reimplementation'), - 'branch-name' => 'master', - 'project-path' => project.path_with_namespace, - 'csp-nonce' => 'test-csp-nonce', - 'ide-remote-path' => ide_remote_path(remote_host: ':remote_host', remote_path: ':remote_path'), - 'file-path' => 'foo/README.md', - 'editor-font-family' => 'JetBrains Mono', - 'editor-font-format' => 'woff2', - 'editor-font-src-url' => a_string_matching(%r{jetbrains-mono/JetBrainsMono}), - 'merge-request' => '7', - 'fork-info' => nil - ) + context 'with project' do + it 'returns hash with parameters' do + serialized_project = API::Entities::Project.represent(project, current_user: user).to_json + + expect( + helper.ide_data(project: project, fork_info: nil, params: params) + ).to include(base_data.merge( + 'fork-info' => nil, + 'branch-name' => params[:branch], + 'file-path' => params[:path], + 'merge-request' => params[:merge_request_id], + 'project' => serialized_project, + 'preview-markdown-path' => Gitlab::Routing.url_helpers.preview_markdown_project_path(project) + )) end - it 'does not use new web ide if user.use_legacy_web_ide' do - allow(user).to receive(:use_legacy_web_ide).and_return(true) - - expect(helper.ide_data(project: project, branch: nil, path: nil, merge_request: nil, -fork_info: nil)).to include('use-new-web-ide' => 'false') + context 'with fork info' do + it 'returns hash with fork info' do + expect(helper.ide_data(project: project, fork_info: fork_info, params: params)) + .to include('fork-info' => fork_info.to_json) + end end end - context 'with vscode_web_ide=false' do + context 'with environments guidance experiment', :experiment do before do - stub_feature_flags(vscode_web_ide: false) + stub_experiments(in_product_guidance_environments_webide: :candidate) end - context 'when instance vars and parameters are not set' do - it 'returns instance data in the hash as nil' do - expect(helper.ide_data(project: nil, branch: nil, path: nil, merge_request: nil, fork_info: nil)) - .to include( - 'can-use-new-web-ide' => 'false', - 'use-new-web-ide' => 'false', - 'user-preferences-path' => profile_preferences_path, - 'branch-name' => nil, - 'file-path' => nil, - 'merge-request' => nil, - 'fork-info' => nil, - 'project' => nil, - 'preview-markdown-path' => nil - ) + context 'when project has no enviornments' do + it 'enables environment guidance' do + expect(helper.ide_data(project: project, fork_info: fork_info, params: params)) + .to include('enable-environments-guidance' => 'true') end - end - context 'when instance vars are set' do - it 'returns instance data in the hash' do - fork_info = { ide_path: '/test/ide/path' } - - serialized_project = API::Entities::Project.represent(project, current_user: project.creator).to_json - - expect(helper.ide_data(project: project, branch: 'master', path: 'foo/bar', merge_request: '1', -fork_info: fork_info)) - .to include( - 'branch-name' => 'master', - 'file-path' => 'foo/bar', - 'merge-request' => '1', - 'fork-info' => fork_info.to_json, - 'project' => serialized_project, - 'preview-markdown-path' => Gitlab::Routing.url_helpers.preview_markdown_project_path(project) - ) + context 'and the callout has been dismissed' do + it 'disables environment guidance' do + callout = create(:callout, feature_name: :web_ide_ci_environments_guidance, user: user) + callout.update!(dismissed_at: Time.now - 1.week) + allow(helper).to receive(:current_user).and_return(User.find(user.id)) + + expect(helper.ide_data(project: project, fork_info: fork_info, params: params)) + .to include('enable-environments-guidance' => 'false') + end end end - context 'environments guidance experiment', :experiment do - before do - stub_experiments(in_product_guidance_environments_webide: :candidate) + context 'when the project has environments' do + it 'disables environment guidance' do + create(:environment, project: project) + + expect(helper.ide_data(project: project, fork_info: fork_info, params: params)) + .to include('enable-environments-guidance' => 'false') end + end + end - context 'when project has no enviornments' do - it 'enables environment guidance' do - expect(helper.ide_data(project: project, branch: nil, path: nil, merge_request: nil, -fork_info: nil)).to include('enable-environments-guidance' => 'true') - end + context 'with vscode_web_ide=true' do + let(:base_data) do + { + 'use-new-web-ide' => 'true', + 'user-preferences-path' => profile_preferences_path, + 'sign-in-path' => 'test-sign-in-path', + 'new-web-ide-help-page-path' => + help_page_path('user/project/web_ide/index.md', anchor: 'vscode-reimplementation'), + 'csp-nonce' => 'test-csp-nonce', + 'ide-remote-path' => ide_remote_path(remote_host: ':remote_host', remote_path: ':remote_path'), + 'editor-font-family' => 'JetBrains Mono', + 'editor-font-format' => 'woff2', + 'editor-font-src-url' => a_string_matching(%r{jetbrains-mono/JetBrainsMono}) + } + end - context 'and the callout has been dismissed' do - it 'disables environment guidance' do - callout = create(:callout, feature_name: :web_ide_ci_environments_guidance, user: project.creator) - callout.update!(dismissed_at: Time.now - 1.week) - allow(helper).to receive(:current_user).and_return(User.find(project.creator.id)) - expect(helper.ide_data(project: project, branch: nil, path: nil, merge_request: nil, -fork_info: nil)).to include('enable-environments-guidance' => 'false') - end - end - end + before do + stub_feature_flags(vscode_web_ide: true) + end - context 'when the project has environments' do - it 'disables environment guidance' do - create(:environment, project: project) + it 'returns hash' do + expect(helper.ide_data(project: nil, fork_info: fork_info, params: params)) + .to include(base_data) + end - expect(helper.ide_data(project: project, branch: nil, path: nil, merge_request: nil, -fork_info: nil)).to include('enable-environments-guidance' => 'false') - end + it 'does not use new web ide if feature flag is disabled' do + stub_feature_flags(vscode_web_ide: false) + + expect(helper.ide_data(project: nil, fork_info: fork_info, params: params)) + .to include('use-new-web-ide' => 'false') + end + + context 'with project' do + it 'returns hash with parameters' do + expect( + helper.ide_data(project: project, fork_info: nil, params: params) + ).to include(base_data.merge( + 'branch-name' => params[:branch], + 'file-path' => params[:path], + 'merge-request' => params[:merge_request_id], + 'fork-info' => nil + )) end end end diff --git a/spec/helpers/integrations_helper_spec.rb b/spec/helpers/integrations_helper_spec.rb index 9822f9fac05..4f1e6c86fea 100644 --- a/spec/helpers/integrations_helper_spec.rb +++ b/spec/helpers/integrations_helper_spec.rb @@ -165,17 +165,18 @@ RSpec.describe IntegrationsHelper do with_them do before do - issue.update!(issue_type: issue_type) + issue.assign_attributes(issue_type: issue_type, work_item_type: WorkItems::Type.default_by_type(issue_type)) + issue.save!(validate: false) end it "return the correct i18n issue type" do - expect(described_class.integration_issue_type(issue.issue_type)).to eq(expected_i18n_issue_type) + expect(described_class.integration_issue_type(issue.work_item_type.base_type)).to eq(expected_i18n_issue_type) end end it "only consider these enumeration values are valid" do expected_valid_types = %w[issue incident test_case requirement task objective key_result] - expect(Issue.issue_types.keys).to contain_exactly(*expected_valid_types) + expect(WorkItems::Type.base_types.keys).to contain_exactly(*expected_valid_types) end end diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index 1ae834c0769..ffaffa251d1 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -293,10 +293,13 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do end describe '#issuable_reference' do + let(:project_namespace) { build_stubbed(:project_namespace) } + let(:project) { build_stubbed(:project, project_namespace: project_namespace) } + context 'when show_full_reference truthy' do it 'display issuable full reference' do assign(:show_full_reference, true) - issue = build_stubbed(:issue) + issue = build_stubbed(:issue, project: project) expect(helper.issuable_reference(issue)).to eql(issue.to_reference(full: true)) end @@ -305,12 +308,10 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do context 'when show_full_reference falsey' do context 'when @group present' do it 'display issuable reference to @group' do - project = build_stubbed(:project) - assign(:show_full_reference, nil) assign(:group, project.namespace) - issue = build_stubbed(:issue) + issue = build_stubbed(:issue, project: project) expect(helper.issuable_reference(issue)).to eql(issue.to_reference(project.namespace)) end @@ -318,13 +319,11 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do context 'when @project present' do it 'display issuable reference to @project' do - project = build_stubbed(:project) - assign(:show_full_reference, nil) assign(:group, nil) assign(:project, project) - issue = build_stubbed(:issue) + issue = build_stubbed(:issue, project: project) expect(helper.issuable_reference(issue)).to eql(issue.to_reference(project)) end @@ -333,8 +332,11 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do end describe '#issuable_project_reference' do + let(:project_namespace) { build_stubbed(:project_namespace) } + let(:project) { build_stubbed(:project, project_namespace: project_namespace) } + it 'display project name and simple reference with `#` to an issue' do - issue = build_stubbed(:issue) + issue = build_stubbed(:issue, project: project) expect(helper.issuable_project_reference(issue)).to eq("#{issue.project.full_name} ##{issue.iid}") end @@ -414,7 +416,7 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do initialTitleText: issue.title, initialDescriptionHtml: '

issue text

', initialDescriptionText: 'issue text', - initialTaskStatus: '0 of 0 checklist items completed', + initialTaskCompletionStatus: { completed_count: 0, count: 0 }, issueType: 'issue', iid: issue.iid.to_s, isHidden: false @@ -430,7 +432,8 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do action: "show", namespace_id: "foo", project_id: "bar", - id: incident.iid + id: incident.iid, + incident_tab: 'timeline' }).permit! end @@ -441,7 +444,9 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do expected_data = { issueType: 'incident', hasLinkedAlerts: false, - canUpdateTimelineEvent: true + canUpdateTimelineEvent: true, + currentPath: "/foo/bar/-/issues/incident/#{incident.iid}/timeline", + currentTab: 'timeline' } expect(helper.issuable_initial_data(incident)).to match(hash_including(expected_data)) @@ -690,4 +695,94 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do end end end + + describe '#issuable_type_selector_data' do + using RSpec::Parameterized::TableSyntax + + let_it_be(:project) { create(:project) } + + where(:issuable_type, :issuable_display_type, :is_issue_allowed, :is_incident_allowed) do + :issue | 'issue' | true | false + :incident | 'incident' | false | true + end + + with_them do + let(:issuable) { build_stubbed(issuable_type) } + + before do + allow(helper).to receive(:create_issue_type_allowed?).with(project, :issue).and_return(is_issue_allowed) + allow(helper).to receive(:create_issue_type_allowed?).with(project, :incident).and_return(is_incident_allowed) + assign(:project, project) + end + + it 'returns the correct data for the issuable type selector' do + expected_data = { + selected_type: issuable_display_type, + is_issue_allowed: is_issue_allowed.to_s, + is_incident_allowed: is_incident_allowed.to_s, + issue_path: new_project_issue_path(project), + incident_path: new_project_issue_path(project, { issuable_template: 'incident', issue: { issue_type: 'incident' } }) + } + + expect(helper.issuable_type_selector_data(issuable)).to match(expected_data) + end + end + end + + describe '#issuable_label_selector_data' do + let_it_be(:project) { create(:project, :repository) } + + context 'with a new issuable' do + let_it_be(:issuable) { build(:issue, project: project) } + + it 'returns the expected data' do + expect(helper.issuable_label_selector_data(project, issuable)).to match({ + field_name: "#{issuable.class.model_name.param_key}[label_ids][]", + full_path: project.full_path, + initial_labels: '[]', + issuable_type: issuable.issuable_type, + labels_filter_base_path: project_issues_path(project), + labels_manage_path: project_labels_path(project) + }) + end + end + + context 'with an existing issuable' do + let_it_be(:label) { create(:label, name: 'Bug') } + let_it_be(:label2) { create(:label, name: 'Community contribution') } + let_it_be(:issuable) do + create(:merge_request, source_project: project, target_project: project, labels: [label, label2]) + end + + it 'returns the expected data' do + initial_labels = [ + { + __typename: "Label", + id: label.id, + title: label.title, + description: label.description, + color: label.color, + text_color: label.text_color + }, + { + __typename: "Label", + id: label2.id, + title: label2.title, + description: label2.description, + color: label2.color, + text_color: label2.text_color + } + ] + + expect(helper.issuable_label_selector_data(project, issuable)).to match({ + field_name: "#{issuable.class.model_name.param_key}[label_ids][]", + full_path: project.full_path, + initial_labels: initial_labels.to_json, + issuable_type: issuable.issuable_type, + labels_filter_base_path: project_merge_requests_path(project), + labels_manage_path: project_labels_path(project) + }) + end + end + end end diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 994a1ff4f75..38cbb5a1d66 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -3,21 +3,11 @@ require 'spec_helper' RSpec.describe IssuesHelper do + include Features::MergeRequestHelpers + let_it_be(:project) { create(:project) } let_it_be_with_reload(:issue) { create(:issue, project: project) } - describe '#work_item_type_icon' do - it 'returns icon of all standard base types' do - WorkItems::Type.base_types.each do |type| - expect(work_item_type_icon(type[0])).to eq "issue-type-#{type[0].to_s.dasherize}" - end - end - - it 'defaults to issue icon if type is unknown' do - expect(work_item_type_icon('invalid')).to eq 'issue-type-issue' - end - end - describe '#award_user_list' do it 'returns a comma-separated list of the first X users' do user = build_stubbed(:user, name: 'Joe') @@ -228,8 +218,8 @@ RSpec.describe IssuesHelper do let!(:new_issue) { create(:issue, author: User.support_bot, project: project2) } before do - allow(Gitlab::IncomingEmail).to receive(:enabled?) { true } - allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true } + allow(Gitlab::Email::IncomingEmail).to receive(:enabled?) { true } + allow(Gitlab::Email::IncomingEmail).to receive(:supports_wildcard?) { true } old_issue.update!(moved_to: new_issue) end @@ -247,10 +237,13 @@ RSpec.describe IssuesHelper do describe '#issue_header_actions_data' do let(:current_user) { create(:user) } + let(:merge_request) { create(:merge_request, :opened, source_project: project, author: current_user) } + let(:issuable_sidebar_issue) { serialize_issuable_sidebar(current_user, project, merge_request) } before do allow(helper).to receive(:current_user).and_return(current_user) allow(helper).to receive(:can?).and_return(true) + allow(helper).to receive(:issuable_sidebar).and_return(issuable_sidebar_issue) end it 'returns expected result' do @@ -269,10 +262,11 @@ RSpec.describe IssuesHelper do report_abuse_path: add_category_abuse_reports_path, reported_user_id: issue.author.id, reported_from_url: issue_url(issue), - submit_as_spam_path: mark_as_spam_project_issue_path(project, issue) + submit_as_spam_path: mark_as_spam_project_issue_path(project, issue), + issuable_email_address: issuable_sidebar_issue[:create_note_email] } - expect(helper.issue_header_actions_data(project, issue, current_user)).to include(expected) + expect(helper.issue_header_actions_data(project, issue, current_user, issuable_sidebar_issue)).to include(expected) end end diff --git a/spec/helpers/jira_connect_helper_spec.rb b/spec/helpers/jira_connect_helper_spec.rb index 31aeff85c70..b7c25320a0e 100644 --- a/spec/helpers/jira_connect_helper_spec.rb +++ b/spec/helpers/jira_connect_helper_spec.rb @@ -9,8 +9,7 @@ RSpec.describe JiraConnectHelper, feature_category: :integrations do let(:user) { create(:user) } let(:client_id) { '123' } - let(:enable_public_keys_storage_config) { false } - let(:enable_public_keys_storage_setting) { false } + let(:enable_public_keys_storage) { false } before do stub_application_setting(jira_connect_application_key: client_id) @@ -22,26 +21,18 @@ RSpec.describe JiraConnectHelper, feature_category: :integrations do before do allow(view).to receive(:current_user).and_return(nil) allow(Gitlab.config.gitlab).to receive(:url).and_return('http://test.host') - allow(Gitlab.config.jira_connect).to receive(:enable_public_keys_storage) - .and_return(enable_public_keys_storage_config) - stub_application_setting(jira_connect_public_key_storage_enabled: enable_public_keys_storage_setting) + stub_application_setting(jira_connect_public_key_storage_enabled: enable_public_keys_storage) end it 'includes Jira Connect app attributes' do is_expected.to include( :groups_path, - :add_subscriptions_path, :subscriptions_path, - :users_path, :subscriptions, :gitlab_user_path ) end - it 'assigns users_path with value' do - expect(subject[:users_path]).to eq(jira_connect_users_path) - end - context 'with oauth_metadata' do let(:oauth_metadata) { helper.jira_connect_app_data([subscription], installation)[:oauth_metadata] } @@ -72,16 +63,6 @@ RSpec.describe JiraConnectHelper, feature_category: :integrations do ) end - context 'jira_connect_oauth feature is disabled' do - before do - stub_feature_flags(jira_connect_oauth: false) - end - - it 'does not assign oauth_metadata' do - expect(oauth_metadata).to be_nil - end - end - context 'with self-managed instance' do let_it_be(:installation) { create(:jira_connect_installation, instance_url: 'https://gitlab.example.com') } @@ -108,16 +89,8 @@ RSpec.describe JiraConnectHelper, feature_category: :integrations do expect(subject[:public_key_storage_enabled]).to eq(false) end - context 'when public_key_storage is enabled via config' do - let(:enable_public_keys_storage_config) { true } - - it 'assignes public_key_storage_enabled to true' do - expect(subject[:public_key_storage_enabled]).to eq(true) - end - end - - context 'when public_key_storage is enabled via setting' do - let(:enable_public_keys_storage_setting) { true } + context 'when public_key_storage is enabled' do + let(:enable_public_keys_storage) { true } it 'assignes public_key_storage_enabled to true' do expect(subject[:public_key_storage_enabled]).to eq(true) diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index e8e981251e3..b4549630813 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -62,19 +62,19 @@ RSpec.describe LabelsHelper do end context 'with a project as subject' do - let(:namespace) { build(:namespace, name: 'foo3') } - let(:subject) { build(:project, namespace: namespace, name: 'bar3') } + let(:namespace) { build(:namespace) } + let(:subject) { build(:project, namespace: namespace) } it 'links to project issues page' do - expect(link_to_label(label_presenter)).to match %r{.*}m + expect(link_to_label(label_presenter)).to match %r{.*}m end end context 'with a group as subject' do - let(:subject) { build(:group, name: 'bar') } + let(:subject) { build(:group) } it 'links to group issues page' do - expect(link_to_label(label_presenter)).to match %r{.*}m + expect(link_to_label(label_presenter)).to match %r{.*}m end end @@ -126,11 +126,11 @@ RSpec.describe LabelsHelper do end it 'uses dark text on light backgrounds' do - expect(text_color_for_bg('#EEEEEE')).to be_color('#333333') + expect(text_color_for_bg('#EEEEEE')).to be_color('#1F1E24') end it 'supports RGB triplets' do - expect(text_color_for_bg('#FFF')).to be_color '#333333' + expect(text_color_for_bg('#FFF')).to be_color '#1F1E24' expect(text_color_for_bg('#000')).to be_color '#FFFFFF' end end diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index 088519248c6..a9f99f29f6d 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -534,6 +534,45 @@ RSpec.describe MarkupHelper do helper.first_line_in_markdown(object, attribute, 100, is_todo: true, project: project) end.not_to change { Gitlab::GitalyClient.get_request_count } end + + it 'strips non-user links' do + html = 'This a cool [website](https://gitlab.com/).' + + object = create_object(html) + result = helper.first_line_in_markdown(object, attribute, 100, is_todo: true, project: project) + + expect(result).to include('This a cool website.') + end + + it 'styles the current user link', :aggregate_failures do + another_user = create(:user) + html = "Please have a look, @#{user.username} @#{another_user.username}!" + + object = create_object(html) + result = helper.first_line_in_markdown(object, attribute, 100, is_todo: true, project: project) + links = Nokogiri::HTML.parse(result).css('//a') + + expect(links[0].classes).to include('current-user') + expect(links[1].classes).not_to include('current-user') + end + + context 'when current_user is nil' do + before do + allow(helper).to receive(:current_user).and_return(nil) + end + + it 'renders the link with no styling when current_user is nil' do + another_user = create(:user) + html = "Please have a look, @#{user.username} @#{another_user.username}!" + + object = create_object(html) + result = helper.first_line_in_markdown(object, attribute, 100, is_todo: true, project: project) + links = Nokogiri::HTML.parse(result).css('//a') + + expect(links[0].classes).not_to include('current-user') + expect(links[1].classes).not_to include('current-user') + end + end end context 'when the asked attribute can be redacted' do diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb index 6b43e97a0b4..93df9d5f94b 100644 --- a/spec/helpers/merge_requests_helper_spec.rb +++ b/spec/helpers/merge_requests_helper_spec.rb @@ -3,7 +3,14 @@ require 'spec_helper' RSpec.describe MergeRequestsHelper, feature_category: :code_review_workflow do + include Users::CalloutsHelper + include ApplicationHelper + include PageLayoutHelper + include ProjectsHelper include ProjectForksHelper + include IconsHelper + + let_it_be(:current_user) { create(:user) } describe '#format_mr_branch_names' do describe 'within the same project' do @@ -27,7 +34,31 @@ RSpec.describe MergeRequestsHelper, feature_category: :code_review_workflow do end end + describe '#diffs_tab_pane_data' do + subject { diffs_tab_pane_data(project, merge_request, {}) } + + context 'for endpoint_diff_for_path' do + context 'when sub-group project namespace' do + let_it_be(:group) { create(:group, :public) } + let_it_be(:subgroup) { create(:group, :private, parent: group) } + let_it_be(:project) { create(:project, :private, group: subgroup) } + let_it_be(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + + it 'returns expected values' do + expect( + subject[:endpoint_diff_for_path] + ).to include("#{project.full_path}/-/merge_requests/#{merge_request.iid}/diff_for_path.json") + end + end + end + end + describe '#merge_path_description' do + # Using let_it_be(:project) raises the following error, so we use need to use let(:project): + # ActiveRecord::InvalidForeignKey: + # PG::ForeignKeyViolation: ERROR: insert or update on table "fork_network_members" violates foreign key + # constraint "fk_rails_a40860a1ca" + # DETAIL: Key (fork_network_id)=(8) is not present in table "fork_networks". let(:project) { create(:project) } let(:forked_project) { fork_project(project) } let(:merge_request_forked) { create(:merge_request, source_project: forked_project, target_project: project) } @@ -150,4 +181,45 @@ RSpec.describe MergeRequestsHelper, feature_category: :code_review_workflow do end end end + + describe '#merge_request_source_branch' do + let_it_be(:project) { create(:project) } + let(:forked_project) { fork_project(project) } + let(:merge_request_forked) { create(:merge_request, source_project: forked_project, target_project: project) } + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + + context 'when merge request is a fork' do + subject { merge_request_source_branch(merge_request_forked) } + + it 'does show the fork icon' do + expect(subject).to match(/fork/) + end + end + + context 'when merge request is not a fork' do + subject { merge_request_source_branch(merge_request) } + + it 'does not show the fork icon' do + expect(subject).not_to match(/fork/) + end + end + end + + describe '#tab_count_display' do + let(:merge_request) { create(:merge_request) } + + context 'when merge request is preparing' do + before do + allow(merge_request).to receive(:preparing?).and_return(true) + end + + it { expect(tab_count_display(merge_request, 0)).to eq('-') } + it { expect(tab_count_display(merge_request, '0')).to eq('-') } + end + + context 'when merge request is prepared' do + it { expect(tab_count_display(merge_request, 10)).to eq(10) } + it { expect(tab_count_display(merge_request, '10')).to eq('10') } + end + end end diff --git a/spec/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb index 3e6780d6831..e7c8e40da7f 100644 --- a/spec/helpers/namespaces_helper_spec.rb +++ b/spec/helpers/namespaces_helper_spec.rb @@ -2,42 +2,39 @@ require 'spec_helper' -RSpec.describe NamespacesHelper do +RSpec.describe NamespacesHelper, feature_category: :subgroups do let!(:admin) { create(:admin) } let!(:admin_project_creation_level) { nil } let!(:admin_group) do - create(:group, - :private, - project_creation_level: admin_project_creation_level) + create(:group, :private, project_creation_level: admin_project_creation_level) end let!(:user) { create(:user) } let!(:user_project_creation_level) { nil } let!(:user_group) do - create(:group, - :private, - project_creation_level: user_project_creation_level) + create(:group, :private, project_creation_level: user_project_creation_level) end let!(:subgroup1) do - create(:group, - :private, - parent: admin_group, - project_creation_level: nil) + create(:group, :private, parent: admin_group, project_creation_level: nil) end let!(:subgroup2) do - create(:group, - :private, - parent: admin_group, - project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) + create( + :group, + :private, + parent: admin_group, + project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS + ) end let!(:subgroup3) do - create(:group, - :private, - parent: admin_group, - project_creation_level: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) + create( + :group, + :private, + parent: admin_group, + project_creation_level: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS + ) end before do @@ -124,7 +121,7 @@ RSpec.describe NamespacesHelper do end end - describe '#pipeline_usage_app_data' do + describe '#pipeline_usage_app_data', unless: Gitlab.ee?, feature_category: :consumables_cost_management do it 'returns a hash with necessary data for the frontend' do expect(helper.pipeline_usage_app_data(user_group)).to eql({ namespace_actual_plan_name: user_group.actual_plan_name, diff --git a/spec/helpers/nav/new_dropdown_helper_spec.rb b/spec/helpers/nav/new_dropdown_helper_spec.rb index 3a66fe474ab..5ae057dc97d 100644 --- a/spec/helpers/nav/new_dropdown_helper_spec.rb +++ b/spec/helpers/nav/new_dropdown_helper_spec.rb @@ -11,8 +11,11 @@ RSpec.describe Nav::NewDropdownHelper, feature_category: :navigation do let(:with_can_create_project) { false } let(:with_can_create_group) { false } let(:with_can_create_snippet) { false } + let(:title) { 'Create new...' } - subject(:view_model) { helper.new_dropdown_view_model(project: current_project, group: current_group) } + subject(:view_model) do + helper.new_dropdown_view_model(project: current_project, group: current_group) + end before do allow(helper).to receive(:current_user) { current_user } @@ -22,7 +25,7 @@ RSpec.describe Nav::NewDropdownHelper, feature_category: :navigation do allow(user).to receive(:can?).with(:create_snippet) { with_can_create_snippet } end - shared_examples 'invite member item' do + shared_examples 'invite member item' do |partial| it 'shows invite member link with emoji' do expect(view_model[:menu_sections]).to eq( expected_menu_section( @@ -30,12 +33,12 @@ RSpec.describe Nav::NewDropdownHelper, feature_category: :navigation do menu_item: ::Gitlab::Nav::TopNavMenuItem.build( id: 'invite', title: 'Invite members', - emoji: 'shaking_hands', - href: expected_href, + icon: 'shaking_hands', + partial: partial, + component: 'invite_members', data: { - track_action: 'click_link_invite_members', - track_label: 'plus_menu_dropdown', - track_property: 'navigation_top' + trigger_source: 'top-nav', + trigger_element: 'text-emoji' } ) ) @@ -54,8 +57,13 @@ RSpec.describe Nav::NewDropdownHelper, feature_category: :navigation do end context 'when group and project are nil' do - it 'has no menu sections' do - expect(view_model[:menu_sections]).to eq([]) + it 'has base results' do + results = { + title: title, + menu_sections: [] + } + + expect(view_model).to eq(results) end context 'when can create project' do @@ -145,8 +153,13 @@ RSpec.describe Nav::NewDropdownHelper, feature_category: :navigation do .to receive(:can?).with(current_user, :admin_group_member, group) { with_can_admin_in_group } end - it 'has no menu sections' do - expect(view_model[:menu_sections]).to eq([]) + it 'has base results' do + results = { + title: title, + menu_sections: [] + } + + expect(view_model).to eq(results) end context 'when can create projects in group' do @@ -199,7 +212,7 @@ RSpec.describe Nav::NewDropdownHelper, feature_category: :navigation do let(:expected_title) { 'In this group' } let(:expected_href) { "/groups/#{group.full_path}/-/group_members" } - it_behaves_like 'invite member item' + it_behaves_like 'invite member item', 'groups/invite_members_top_nav_link' end end @@ -219,8 +232,13 @@ RSpec.describe Nav::NewDropdownHelper, feature_category: :navigation do allow(helper).to receive(:can_admin_project_member?) { with_can_admin_project_member } end - it 'has no menu sections' do - expect(view_model[:menu_sections]).to eq([]) + it 'has base results' do + results = { + title: title, + menu_sections: [] + } + + expect(view_model).to eq(results) end context 'with show_new_issue_link?' do @@ -296,7 +314,7 @@ RSpec.describe Nav::NewDropdownHelper, feature_category: :navigation do let(:expected_title) { 'In this project' } let(:expected_href) { "/#{project.path_with_namespace}/-/project_members" } - it_behaves_like 'invite member item' + it_behaves_like 'invite member item', 'projects/invite_members_top_nav_link' end end @@ -311,22 +329,27 @@ RSpec.describe Nav::NewDropdownHelper, feature_category: :navigation do allow(helper).to receive(:can?).with(current_user, :create_projects, group).and_return(true) end - it 'gives precedence to group over project' do - group_section = expected_menu_section( - title: 'In this group', + it 'gives precedence to project over group' do + project_section = expected_menu_section( + title: 'In this project', menu_item: ::Gitlab::Nav::TopNavMenuItem.build( - id: 'new_project', - title: 'New project/repository', - href: "/projects/new?namespace_id=#{group.id}", + id: 'new_issue', + title: 'New issue', + href: "/#{project.path_with_namespace}/-/issues/new", data: { - track_action: 'click_link_new_project_group', + track_action: 'click_link_new_issue', track_label: 'plus_menu_dropdown', - track_property: 'navigation_top' + track_property: 'navigation_top', + qa_selector: 'new_issue_link' } ) ) + results = { + title: title, + menu_sections: project_section + } - expect(view_model[:menu_sections]).to eq(group_section) + expect(view_model).to eq(results) end end diff --git a/spec/helpers/nav/top_nav_helper_spec.rb b/spec/helpers/nav/top_nav_helper_spec.rb index ce5ac2e5404..252423aa988 100644 --- a/spec/helpers/nav/top_nav_helper_spec.rb +++ b/spec/helpers/nav/top_nav_helper_spec.rb @@ -56,6 +56,7 @@ RSpec.describe Nav::TopNavHelper do expected_primary = [ { href: '/explore', icon: 'project', id: 'project', title: 'Projects' }, { href: '/explore/groups', icon: 'group', id: 'groups', title: 'Groups' }, + { href: '/explore/projects/topics', icon: 'labels', id: 'topics', title: 'Topics' }, { href: '/explore/snippets', icon: 'snippet', id: 'snippets', title: 'Snippets' } ].map do |item| ::Gitlab::Nav::TopNavMenuItem.build(**item) @@ -78,6 +79,12 @@ RSpec.describe Nav::TopNavHelper do title: 'Groups', css_class: 'dashboard-shortcuts-groups' }, + { + href: '/explore/projects/topics', + id: 'topics-shortcut', + title: 'Topics', + css_class: 'dashboard-shortcuts-topics' + }, { href: '/explore/snippets', id: 'snippets-shortcut', @@ -320,20 +327,6 @@ RSpec.describe Nav::TopNavHelper do context 'with milestones' do let(:with_milestones) { true } - it 'has expected :primary' do - expected_header = ::Gitlab::Nav::TopNavMenuHeader.build( - title: 'Explore' - ) - expected_primary = ::Gitlab::Nav::TopNavMenuItem.build( - data: { **menu_data_tracking_attrs('milestones') }, - href: '/dashboard/milestones', - icon: 'clock', - id: 'milestones', - title: 'Milestones' - ) - expect(subject[:primary]).to eq([expected_header, expected_primary]) - end - it 'has expected :shortcuts' do expected_shortcuts = ::Gitlab::Nav::TopNavMenuItem.build( id: 'milestones-shortcut', @@ -348,23 +341,6 @@ RSpec.describe Nav::TopNavHelper do context 'with snippets' do let(:with_snippets) { true } - it 'has expected :primary' do - expected_header = ::Gitlab::Nav::TopNavMenuHeader.build( - title: 'Explore' - ) - expected_primary = ::Gitlab::Nav::TopNavMenuItem.build( - data: { - qa_selector: 'snippets_link', - **menu_data_tracking_attrs('snippets') - }, - href: '/dashboard/snippets', - icon: 'snippet', - id: 'snippets', - title: 'Snippets' - ) - expect(subject[:primary]).to eq([expected_header, expected_primary]) - end - it 'has expected :shortcuts' do expected_shortcuts = ::Gitlab::Nav::TopNavMenuItem.build( id: 'snippets-shortcut', @@ -379,20 +355,6 @@ RSpec.describe Nav::TopNavHelper do context 'with activity' do let(:with_activity) { true } - it 'has expected :primary' do - expected_header = ::Gitlab::Nav::TopNavMenuHeader.build( - title: 'Explore' - ) - expected_primary = ::Gitlab::Nav::TopNavMenuItem.build( - data: { **menu_data_tracking_attrs('activity') }, - href: '/dashboard/activity', - icon: 'history', - id: 'activity', - title: 'Activity' - ) - expect(subject[:primary]).to eq([expected_header, expected_primary]) - end - it 'has expected :shortcuts' do expected_shortcuts = ::Gitlab::Nav::TopNavMenuItem.build( id: 'activity-shortcut', @@ -431,7 +393,7 @@ RSpec.describe Nav::TopNavHelper do it 'has leave_admin_mode as last :secondary item' do expected_leave_admin_mode_item = ::Gitlab::Nav::TopNavMenuItem.build( id: 'leave_admin_mode', - title: 'Leave Admin Mode', + title: 'Leave admin mode', icon: 'lock-open', href: '/admin/session/destroy', data: { method: 'post', **menu_data_tracking_attrs('leave_admin_mode') } @@ -447,11 +409,11 @@ RSpec.describe Nav::TopNavHelper do expected_enter_admin_mode_item = ::Gitlab::Nav::TopNavMenuItem.build( data: { qa_selector: 'menu_item_link', - qa_title: 'Enter Admin Mode', + qa_title: 'Enter admin mode', **menu_data_tracking_attrs('enter_admin_mode') }, id: 'enter_admin_mode', - title: 'Enter Admin Mode', + title: 'Enter admin mode', icon: 'lock', href: '/admin/session/new' ) diff --git a/spec/helpers/nav_helper_spec.rb b/spec/helpers/nav_helper_spec.rb index adf784360c2..17d28b07763 100644 --- a/spec/helpers/nav_helper_spec.rb +++ b/spec/helpers/nav_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe NavHelper do +RSpec.describe NavHelper, feature_category: :navigation do describe '#header_links' do include_context 'custom session' @@ -136,23 +136,8 @@ RSpec.describe NavHelper do end describe '#show_super_sidebar?' do - shared_examples '#show_super_sidebar returns false' do - it 'returns false' do - expect(helper.show_super_sidebar?).to eq(false) - end - end - - it 'returns false by default' do - allow(helper).to receive(:current_user).and_return(nil) - - expect(helper.show_super_sidebar?).to be_falsy - end - - context 'when used is signed-in' do - let_it_be(:user) { create(:user) } - + shared_examples 'show_super_sidebar is supposed to' do before do - allow(helper).to receive(:current_user).and_return(user) stub_feature_flags(super_sidebar_nav: new_nav_ff) user.update!(use_new_navigation: user_preference) end @@ -163,33 +148,78 @@ RSpec.describe NavHelper do context 'when user has new nav disabled' do let(:user_preference) { false } - it_behaves_like '#show_super_sidebar returns false' + specify { expect(subject).to eq false } end context 'when user has new nav enabled' do let(:user_preference) { true } - it_behaves_like '#show_super_sidebar returns false' + specify { expect(subject).to eq false } end end context 'with feature flag on' do let(:new_nav_ff) { true } + context 'when user has not interacted with the new nav toggle yet' do + let(:user_preference) { nil } + + specify { expect(subject).to eq false } + + context 'when the user was enrolled into the new nav via a special feature flag' do + before do + # this ff is disabled in globally to keep tests of the old nav working + stub_feature_flags(super_sidebar_nav_enrolled: true) + end + + specify { expect(subject).to eq true } + end + end + context 'when user has new nav disabled' do let(:user_preference) { false } - it_behaves_like '#show_super_sidebar returns false' + specify { expect(subject).to eq false } end context 'when user has new nav enabled' do let(:user_preference) { true } - it 'returns true' do - expect(helper.show_super_sidebar?).to eq(true) - end + specify { expect(subject).to eq true } end end end + + context 'when nil is provided' do + specify { expect(helper.show_super_sidebar?(nil)).to eq false } + end + + context 'when no user is signed-in' do + specify do + allow(helper).to receive(:current_user).and_return(nil) + + expect(helper.show_super_sidebar?).to eq false + end + end + + context 'when user is signed-in' do + let_it_be(:user) { create(:user) } + + context 'with current_user as a default' do + before do + allow(helper).to receive(:current_user).and_return(user) + end + + subject { helper.show_super_sidebar? } + + it_behaves_like 'show_super_sidebar is supposed to' + end + + context 'with user provided as an argument' do + subject { helper.show_super_sidebar?(user) } + + it_behaves_like 'show_super_sidebar is supposed to' + end + end end end diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb index 68a6b6293c8..91635ffcdc0 100644 --- a/spec/helpers/notes_helper_spec.rb +++ b/spec/helpers/notes_helper_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe NotesHelper do +RSpec.describe NotesHelper, feature_category: :team_planning do include RepoHelpers let_it_be(:owner) { create(:owner) } @@ -223,6 +223,17 @@ RSpec.describe NotesHelper do end end + describe '#initial_notes_data' do + it 'return initial notes data for issuable' do + autocomplete = '/autocomplete/users' + @project = project + @noteable = create(:issue, project: @project) + + expect(helper.initial_notes_data(autocomplete).keys).to match_array(%i[notesUrl now diffView enableGFM]) + expect(helper.initial_notes_data(autocomplete)[:enableGFM].keys).to match(%i[emojis members issues mergeRequests vulnerabilities epics milestones labels]) + end + end + describe '#notes_url' do it 'return snippet notes path for personal snippet' do @snippet = create(:personal_snippet) diff --git a/spec/helpers/notify_helper_spec.rb b/spec/helpers/notify_helper_spec.rb index 09da2b89dff..bc1b927cc93 100644 --- a/spec/helpers/notify_helper_spec.rb +++ b/spec/helpers/notify_helper_spec.rb @@ -64,10 +64,19 @@ RSpec.describe NotifyHelper do mr_link_style = "font-weight: 600;color:#3777b0;text-decoration:none" reviewer_avatar_style = "border-radius:12px;margin:-7px 0 -7px 3px;" mr_link = link_to(merge_request.to_reference, merge_request_url(merge_request), style: mr_link_style).html_safe - reviewer_avatar = content_tag(:img, nil, height: "24", src: avatar_icon_for_user, style: reviewer_avatar_style, \ - width: "24", alt: "Avatar", class: "avatar").html_safe - reviewer_link = link_to(reviewer.name, user_url(reviewer), style: "color:#333333;text-decoration:none;", \ - class: "muted").html_safe + reviewer_avatar = content_tag( + :img, + nil, + height: "24", + src: avatar_icon_for_user, + style: reviewer_avatar_style, + width: "24", + alt: "Avatar", + class: "avatar" + ).html_safe + reviewer_link = link_to( + reviewer.name, user_url(reviewer), style: "color:#333333;text-decoration:none;", class: "muted" + ).html_safe result = helper.merge_request_hash_param(merge_request, reviewer) expect(result[:mr_highlight]).to eq ''.html_safe expect(result[:highlight_end]).to eq ''.html_safe diff --git a/spec/helpers/packages_helper_spec.rb b/spec/helpers/packages_helper_spec.rb index fc69aee4e04..ae8a7f0c14c 100644 --- a/spec/helpers/packages_helper_spec.rb +++ b/spec/helpers/packages_helper_spec.rb @@ -2,8 +2,9 @@ require 'spec_helper' -RSpec.describe PackagesHelper do +RSpec.describe PackagesHelper, feature_category: :package_registry do using RSpec::Parameterized::TableSyntax + include AdminModeHelper let_it_be_with_reload(:project) { create(:project) } let_it_be(:base_url) { "#{Gitlab.config.gitlab.url}/api/v4/" } @@ -38,11 +39,18 @@ RSpec.describe PackagesHelper do describe '#pypi_registry_url' do let_it_be(:base_url_with_token) { base_url.sub('://', '://__token__:@') } + let_it_be(:public_project) { create(:project, :public) } - it 'returns the pypi registry url' do - url = helper.pypi_registry_url(1) + it 'returns the pypi registry url with token when project is private' do + url = helper.pypi_registry_url(project) - expect(url).to eq("#{base_url_with_token}projects/1/packages/pypi/simple") + expect(url).to eq("#{base_url_with_token}projects/#{project.id}/packages/pypi/simple") + end + + it 'returns the pypi registry url without token when project is public' do + url = helper.pypi_registry_url(public_project) + + expect(url).to eq("#{base_url}projects/#{public_project.id}/packages/pypi/simple") end end @@ -120,4 +128,128 @@ RSpec.describe PackagesHelper do it { is_expected.to eq(expected_result) } end end + + describe '#show_container_registry_settings' do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + let_it_be(:admin) { create(:admin) } + + before do + allow(helper).to receive(:current_user) { user } + end + + subject { helper.show_container_registry_settings(project) } + + context 'with container registry config enabled' do + before do + stub_config(registry: { enabled: true }) + end + + context 'when user has permission' do + before do + allow(Ability).to receive(:allowed?).with(user, :admin_container_image, project).and_return(true) + end + + it { is_expected.to be(true) } + end + + context 'when user does not have permission' do + before do + allow(Ability).to receive(:allowed?).with(user, :admin_container_image, project).and_return(false) + end + + it { is_expected.to be(false) } + end + end + + context 'with container registry config disabled' do + before do + stub_config(registry: { enabled: false }) + end + + context 'when user has permission' do + before do + allow(Ability).to receive(:allowed?).with(user, :admin_container_image, project).and_return(true) + end + + it { is_expected.to be(false) } + end + + context 'when user does not have permission' do + before do + allow(Ability).to receive(:allowed?).with(user, :admin_container_image, project).and_return(false) + end + + it { is_expected.to be(false) } + end + end + end + + describe '#show_group_package_registry_settings' do + let_it_be(:group) { create(:group) } + let_it_be(:user) { create(:user) } + let_it_be(:admin) { create(:admin) } + + before do + allow(helper).to receive(:current_user) { user } + end + + subject { helper.show_group_package_registry_settings(group) } + + context 'with package registry config enabled' do + before do + stub_config(packages: { enabled: true }) + end + + context "with admin", :enable_admin_mode do + before do + allow(helper).to receive(:current_user) { admin } + end + + it { is_expected.to be(true) } + end + + context "with owner" do + before do + group.add_owner(user) + end + + it { is_expected.to be(true) } + end + + %i[maintainer developer reporter guest].each do |role| + context "with #{role}" do + before do + group.public_send("add_#{role}", user) + end + + it { is_expected.to be(false) } + end + end + end + + context 'with package registry config disabled' do + before do + stub_config(packages: { enabled: false }) + end + + context "with admin", :enable_admin_mode do + before do + allow(helper).to receive(:current_user) { admin } + end + + it { is_expected.to be(false) } + end + + %i[owner maintainer developer reporter guest].each do |role| + context "with #{role}" do + before do + group.public_send("add_#{role}", user) + end + + it { is_expected.to be(false) } + end + end + end + end end diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb index eb42ce18da0..b14789fd5d2 100644 --- a/spec/helpers/page_layout_helper_spec.rb +++ b/spec/helpers/page_layout_helper_spec.rb @@ -56,11 +56,12 @@ RSpec.describe PageLayoutHelper do end %w(project user group).each do |type| - let(:object) { build(type, trait) } - let(:trait) { :with_avatar } - context "with @#{type} assigned" do + let(:object) { build(type, trait) } + let(:trait) { :with_avatar } + before do + stub_application_setting(gravatar_enabled: false) assign(type, object) end @@ -128,12 +129,14 @@ RSpec.describe PageLayoutHelper do describe 'a bare controller' do it 'returns an empty context' do - expect(search_context).to have_attributes(project: nil, - group: nil, - snippets: [], - project_metadata: {}, - group_metadata: {}, - search_url: '/search') + expect(search_context).to have_attributes( + project: nil, + group: nil, + snippets: [], + project_metadata: {}, + group_metadata: {}, + search_url: '/search' + ) end end end diff --git a/spec/helpers/plan_limits_helper_spec.rb b/spec/helpers/plan_limits_helper_spec.rb new file mode 100644 index 00000000000..b25e97150f8 --- /dev/null +++ b/spec/helpers/plan_limits_helper_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe PlanLimitsHelper, feature_category: :continuous_integration do + describe '#plan_limit_setting_description' do + it 'describes known limits', :aggregate_failures do + [ + :ci_pipeline_size, + :ci_active_jobs, + :ci_project_subscriptions, + :ci_pipeline_schedules, + :ci_needs_size_limit, + :ci_registered_group_runners, + :ci_registered_project_runners, + :pipeline_hierarchy_size + ].each do |limit_name| + expect(helper.plan_limit_setting_description(limit_name)).to be_present + end + end + + it 'raises an ArgumentError on invalid arguments' do + expect { helper.plan_limit_setting_description(:some_invalid_limit) }.to( + raise_error(ArgumentError, /No description/) + ) + end + end +end diff --git a/spec/helpers/profiles_helper_spec.rb b/spec/helpers/profiles_helper_spec.rb index 7de8ca89d3d..ebe86ccb08d 100644 --- a/spec/helpers/profiles_helper_spec.rb +++ b/spec/helpers/profiles_helper_spec.rb @@ -33,28 +33,28 @@ RSpec.describe ProfilesHelper do end it "returns omniauth provider label for users with external attributes" do - stub_omniauth_setting(sync_profile_from_provider: ['cas3']) + stub_omniauth_setting(sync_profile_from_provider: [example_omniauth_provider]) stub_omniauth_setting(sync_profile_attributes: true) - stub_cas_omniauth_provider - cas_user = create(:omniauth_user, provider: 'cas3') - cas_user.create_user_synced_attributes_metadata(provider: 'cas3', name_synced: true, email_synced: true, location_synced: true) - allow(helper).to receive(:current_user).and_return(cas_user) - - expect(helper.attribute_provider_label(:email)).to eq('CAS') - expect(helper.attribute_provider_label(:name)).to eq('CAS') - expect(helper.attribute_provider_label(:location)).to eq('CAS') + stub_auth0_omniauth_provider + auth0_user = create(:omniauth_user, provider: example_omniauth_provider) + auth0_user.create_user_synced_attributes_metadata(provider: example_omniauth_provider, name_synced: true, email_synced: true, location_synced: true) + allow(helper).to receive(:current_user).and_return(auth0_user) + + expect(helper.attribute_provider_label(:email)).to eq(example_omniauth_provider_label) + expect(helper.attribute_provider_label(:name)).to eq(example_omniauth_provider_label) + expect(helper.attribute_provider_label(:location)).to eq(example_omniauth_provider_label) end it "returns the correct omniauth provider label for users with some external attributes" do - stub_omniauth_setting(sync_profile_from_provider: ['cas3']) + stub_omniauth_setting(sync_profile_from_provider: [example_omniauth_provider]) stub_omniauth_setting(sync_profile_attributes: true) - stub_cas_omniauth_provider - cas_user = create(:omniauth_user, provider: 'cas3') - cas_user.create_user_synced_attributes_metadata(provider: 'cas3', name_synced: false, email_synced: true, location_synced: false) - allow(helper).to receive(:current_user).and_return(cas_user) + stub_auth0_omniauth_provider + auth0_user = create(:omniauth_user, provider: example_omniauth_provider) + auth0_user.create_user_synced_attributes_metadata(provider: example_omniauth_provider, name_synced: false, email_synced: true, location_synced: false) + allow(helper).to receive(:current_user).and_return(auth0_user) expect(helper.attribute_provider_label(:name)).to be_nil - expect(helper.attribute_provider_label(:email)).to eq('CAS') + expect(helper.attribute_provider_label(:email)).to eq(example_omniauth_provider_label) expect(helper.attribute_provider_label(:location)).to be_nil end @@ -118,12 +118,20 @@ RSpec.describe ProfilesHelper do end end - def stub_cas_omniauth_provider + def stub_auth0_omniauth_provider provider = OpenStruct.new( - 'name' => 'cas3', - 'label' => 'CAS' + 'name' => example_omniauth_provider, + 'label' => example_omniauth_provider_label ) stub_omniauth_setting(providers: [provider]) end + + def example_omniauth_provider + "auth0" + end + + def example_omniauth_provider_label + "Auth0" + end end diff --git a/spec/helpers/projects/ml/experiments_helper_spec.rb b/spec/helpers/projects/ml/experiments_helper_spec.rb index 8ef81c49fa7..021d518a329 100644 --- a/spec/helpers/projects/ml/experiments_helper_spec.rb +++ b/spec/helpers/projects/ml/experiments_helper_spec.rb @@ -8,8 +8,16 @@ require 'mime/types' RSpec.describe Projects::Ml::ExperimentsHelper, feature_category: :mlops do let_it_be(:project) { create(:project, :private) } let_it_be(:experiment) { create(:ml_experiments, user_id: project.creator, project: project) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:build) { create(:ci_build, pipeline: pipeline) } let_it_be(:candidate0) do - create(:ml_candidates, :with_artifact, experiment: experiment, user: project.creator).tap do |c| + create(:ml_candidates, + :with_artifact, + experiment: experiment, + user: project.creator, + project: project, + ci_build: build + ).tap do |c| c.params.build([{ name: 'param1', value: 'p1' }, { name: 'param2', value: 'p2' }]) c.metrics.create!( [{ name: 'metric1', value: 0.1 }, { name: 'metric2', value: 0.2 }, { name: 'metric3', value: 0.3 }] @@ -18,7 +26,8 @@ RSpec.describe Projects::Ml::ExperimentsHelper, feature_category: :mlops do end let_it_be(:candidate1) do - create(:ml_candidates, experiment: experiment, user: project.creator, name: 'candidate1').tap do |c| + create(:ml_candidates, experiment: experiment, user: project.creator, name: 'candidate1', + project: project).tap do |c| c.params.build([{ name: 'param2', value: 'p3' }, { name: 'param3', value: 'p4' }]) c.metrics.create!(name: 'metric3', value: 0.4) end @@ -34,11 +43,13 @@ RSpec.describe Projects::Ml::ExperimentsHelper, feature_category: :mlops do { 'param1' => 'p1', 'param2' => 'p2', 'metric1' => '0.1000', 'metric2' => '0.2000', 'metric3' => '0.3000', 'artifact' => "/#{project.full_path}/-/packages/#{candidate0.artifact.id}", 'details' => "/#{project.full_path}/-/ml/candidates/#{candidate0.iid}", + 'ci_job' => { 'path' => "/#{project.full_path}/-/jobs/#{build.id}", 'name' => 'test' }, 'name' => candidate0.name, 'created_at' => candidate0.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), 'user' => { 'username' => candidate0.user.username, 'path' => "/#{candidate0.user.username}" } }, { 'param2' => 'p3', 'param3' => 'p4', 'metric3' => '0.4000', 'artifact' => nil, 'details' => "/#{project.full_path}/-/ml/candidates/#{candidate1.iid}", + 'ci_job' => nil, 'name' => candidate1.name, 'created_at' => candidate1.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), 'user' => { 'username' => candidate1.user.username, 'path' => "/#{candidate1.user.username}" } } @@ -77,37 +88,14 @@ RSpec.describe Projects::Ml::ExperimentsHelper, feature_category: :mlops do end end - describe '#show_candidate_view_model' do - let(:candidate) { candidate0 } + describe '#experiment_as_data' do + subject { Gitlab::Json.parse(helper.experiment_as_data(experiment)) } - subject { Gitlab::Json.parse(helper.show_candidate_view_model(candidate))['candidate'] } - - it 'generates the correct params' do - expect(subject['params']).to include( - hash_including('name' => 'param1', 'value' => 'p1'), - hash_including('name' => 'param2', 'value' => 'p2') + it do + is_expected.to eq( + { 'name' => experiment.name, 'path' => "/#{project.full_path}/-/ml/experiments/#{experiment.iid}" } ) end - - it 'generates the correct metrics' do - expect(subject['metrics']).to include( - hash_including('name' => 'metric1', 'value' => 0.1), - hash_including('name' => 'metric2', 'value' => 0.2), - hash_including('name' => 'metric3', 'value' => 0.3) - ) - end - - it 'generates the correct info' do - expected_info = { - 'iid' => candidate.iid, - 'path_to_artifact' => "/#{project.full_path}/-/packages/#{candidate.artifact.id}", - 'experiment_name' => candidate.experiment.name, - 'path_to_experiment' => "/#{project.full_path}/-/ml/experiments/#{experiment.iid}", - 'status' => 'running' - } - - expect(subject['info']).to include(expected_info) - end end describe '#experiments_as_data' do diff --git a/spec/helpers/projects/pipeline_helper_spec.rb b/spec/helpers/projects/pipeline_helper_spec.rb index 35045aaef2a..baebbb21aed 100644 --- a/spec/helpers/projects/pipeline_helper_spec.rb +++ b/spec/helpers/projects/pipeline_helper_spec.rb @@ -20,8 +20,7 @@ RSpec.describe Projects::PipelineHelper do it 'returns pipeline tabs data' do expect(pipeline_tabs_data).to include({ failed_jobs_count: pipeline.failed_builds.count, - failed_jobs_summary: prepare_failed_jobs_summary_data(pipeline.failed_builds), - full_path: project.full_path, + project_path: project.full_path, graphql_resource_etag: graphql_etag_pipeline_path(pipeline), metrics_path: namespace_project_ci_prometheus_metrics_histograms_path(namespace_id: project.namespace, project_id: project, format: :json), pipeline_iid: pipeline.iid, diff --git a/spec/helpers/projects/settings/branch_rules_helper_spec.rb b/spec/helpers/projects/settings/branch_rules_helper_spec.rb new file mode 100644 index 00000000000..35a21f72f11 --- /dev/null +++ b/spec/helpers/projects/settings/branch_rules_helper_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::Settings::BranchRulesHelper, feature_category: :source_code_management do + let_it_be(:project) { build_stubbed(:project) } + + describe '#branch_rules_data' do + subject(:data) { helper.branch_rules_data(project) } + + it 'returns branch rules data' do + expect(data).to match({ + project_path: project.full_path, + protected_branches_path: project_settings_repository_path(project, anchor: 'js-protected-branches-settings'), + approval_rules_path: project_settings_merge_requests_path(project, + anchor: 'js-merge-request-approval-settings'), + status_checks_path: project_settings_merge_requests_path(project, anchor: 'js-merge-request-settings'), + branches_path: project_branches_path(project), + show_status_checks: 'false', + show_approvers: 'false', + show_code_owners: 'false' + }) + end + end +end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 477b5cd7753..3eb1090c9dc 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -6,7 +6,7 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do include ProjectForksHelper include AfterNextHelpers - let_it_be_with_reload(:project) { create(:project) } + let_it_be_with_reload(:project) { create(:project, :repository) } let_it_be_with_refind(:project_with_repo) { create(:project, :repository) } let_it_be(:user) { create(:user) } @@ -212,6 +212,80 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do end end + describe '#last_pipeline_from_status_cache' do + before do + # clear cross-example caches + project_with_repo.pipeline_status.delete_from_cache + project_with_repo.instance_variable_set(:@pipeline_status, nil) + end + + context 'without a pipeline' do + it 'returns nil', :aggregate_failures do + expect(::Gitlab::GitalyClient).to receive(:call).at_least(:once).and_call_original + actual_pipeline = last_pipeline_from_status_cache(project_with_repo) + expect(actual_pipeline).to be_nil + end + + context 'when pipeline_status is loaded' do + before do + project_with_repo.pipeline_status # this loads the status + end + + it 'returns nil without calling gitaly when there is no pipeline', :aggregate_failures do + expect(::Gitlab::GitalyClient).not_to receive(:call) + actual_pipeline = last_pipeline_from_status_cache(project_with_repo) + expect(actual_pipeline).to be_nil + end + end + + context 'when FF load_last_pipeline_from_pipeline_status is disabled' do + before do + stub_feature_flags(last_pipeline_from_pipeline_status: false) + end + + it 'returns nil', :aggregate_failures do + expect(project_with_repo).not_to receive(:pipeline_status) + actual_pipeline = last_pipeline_from_status_cache(project_with_repo) + expect(actual_pipeline).to be_nil + end + end + end + + context 'with a pipeline' do + let_it_be(:pipeline) { create(:ci_pipeline, project: project_with_repo) } + + it 'returns the latest pipeline', :aggregate_failures do + expect(::Gitlab::GitalyClient).to receive(:call).at_least(:once).and_call_original + actual_pipeline = last_pipeline_from_status_cache(project_with_repo) + expect(actual_pipeline).to eq pipeline + end + + context 'when pipeline_status is loaded' do + before do + project_with_repo.pipeline_status # this loads the status + end + + it 'returns the latest pipeline without calling gitaly' do + expect(::Gitlab::GitalyClient).not_to receive(:call) + actual_pipeline = last_pipeline_from_status_cache(project_with_repo) + expect(actual_pipeline).to eq pipeline + end + + context 'when FF load_last_pipeline_from_pipeline_status is disabled' do + before do + stub_feature_flags(last_pipeline_from_pipeline_status: false) + end + + it 'returns the latest pipeline', :aggregate_failures do + expect(project_with_repo).not_to receive(:pipeline_status) + actual_pipeline = last_pipeline_from_status_cache(project_with_repo) + expect(actual_pipeline).to eq pipeline + end + end + end + end + end + describe '#show_no_ssh_key_message?' do before do allow(helper).to receive(:current_user).and_return(user) @@ -703,6 +777,34 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do end end + describe '#show_mobile_devops_project_promo?' do + using RSpec::Parameterized::TableSyntax + + where(:hide_cookie, :feature_flag_enabled, :mobile_target_platform, :result) do + false | true | true | true + false | false | true | false + false | false | false | false + false | true | false | false + true | false | false | false + true | true | false | false + true | true | true | false + true | false | true | false + end + + with_them do + before do + allow(Gitlab).to receive(:com?) { gitlab_com } + Feature.enable(:mobile_devops_projects_promo, feature_flag_enabled) + project.project_setting.target_platforms << 'ios' if mobile_target_platform + helper.request.cookies["hide_mobile_devops_promo_#{project.id}"] = true if hide_cookie + end + + it 'resolves if the user can import members' do + expect(helper.show_mobile_devops_project_promo?(project)).to eq result + end + end + end + describe '#can_admin_project_member?' do context 'when user is project owner' do before do @@ -1286,7 +1388,7 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do let_it_be(:has_active_license) { true } it 'displays the correct messagee' do - expect(subject).to eq(s_('Clusters|The certificate-based Kubernetes integration has been deprecated and will be turned off at the end of February 2023. Please %{linkStart}migrate to the GitLab agent for Kubernetes%{linkEnd} or reach out to GitLab support.')) + expect(subject).to eq(s_('Clusters|The certificate-based Kubernetes integration has been deprecated and will be turned off at the end of February 2023. Please %{linkStart}migrate to the GitLab agent for Kubernetes%{linkEnd}. Contact GitLab Support if you have any additional questions.')) end end @@ -1359,23 +1461,99 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do end context 'when fork source is available' do - it 'returns the data related to fork divergence' do - source_project = project_with_repo + let_it_be(:fork_network) { create(:fork_network, root_project: project_with_repo) } + let_it_be(:source_project) { project_with_repo } + + before_all do + project.fork_network = fork_network + project.add_developer(user) + source_project.add_developer(user) + end - allow(helper).to receive(:visible_fork_source).with(project).and_return(source_project) + it 'returns the data related to fork divergence' do + allow(helper).to receive(:current_user).and_return(user) ahead_path = "/#{project.full_path}/-/compare/#{source_project.default_branch}...ref?from_project_id=#{source_project.id}" behind_path = "/#{source_project.full_path}/-/compare/ref...#{source_project.default_branch}?from_project_id=#{project.id}" + create_mr_path = "/#{project.full_path}/-/merge_requests/new?merge_request%5Bsource_branch%5D=ref&merge_request%5Btarget_branch%5D=#{source_project.default_branch}&merge_request%5Btarget_project_id%5D=#{source_project.id}" expect(helper.vue_fork_divergence_data(project, 'ref')).to eq({ + project_path: project.full_path, + selected_branch: 'ref', source_name: source_project.full_name, source_path: project_path(source_project), + can_sync_branch: 'false', ahead_compare_path: ahead_path, - behind_compare_path: behind_path + behind_compare_path: behind_path, + source_default_branch: source_project.default_branch, + create_mr_path: create_mr_path, + view_mr_path: nil }) end + + it 'returns view_mr_path if a merge request for the branch exists' do + allow(helper).to receive(:current_user).and_return(user) + + merge_request = + create(:merge_request, source_project: project, target_project: project_with_repo, + source_branch: project.default_branch, target_branch: project_with_repo.default_branch) + + expect(helper.vue_fork_divergence_data(project, project.default_branch)).to include({ + can_sync_branch: 'true', + create_mr_path: nil, + view_mr_path: "/#{source_project.full_path}/-/merge_requests/#{merge_request.iid}" + }) + end + + context 'when a user cannot create a merge request' do + using RSpec::Parameterized::TableSyntax + + where(:project_role, :source_project_role) do + :guest | :developer + :developer | :guest + end + + with_them do + it 'create_mr_path is nil' do + allow(helper).to receive(:current_user).and_return(user) + + project.add_member(user, project_role) + source_project.add_member(user, source_project_role) + + expect(helper.vue_fork_divergence_data(project, 'ref')).to include({ + create_mr_path: nil, view_mr_path: nil + }) + end + end + end + end + end + + describe '#remote_mirror_setting_enabled?' do + it 'returns false' do + expect(helper.remote_mirror_setting_enabled?).to be_falsy end end + + describe '#http_clone_url_to_repo' do + before do + allow(project).to receive(:http_url_to_repo).and_return('http_url_to_repo') + end + + subject { helper.http_clone_url_to_repo(project) } + + it { expect(subject).to eq('http_url_to_repo') } + end + + describe '#ssh_clone_url_to_repo' do + before do + allow(project).to receive(:ssh_url_to_repo).and_return('ssh_url_to_repo') + end + + subject { helper.ssh_clone_url_to_repo(project) } + + it { expect(subject).to eq('ssh_url_to_repo') } + end end diff --git a/spec/helpers/protected_refs_helper_spec.rb b/spec/helpers/protected_refs_helper_spec.rb new file mode 100644 index 00000000000..820da429107 --- /dev/null +++ b/spec/helpers/protected_refs_helper_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe ProtectedRefsHelper, feature_category: :source_code_management do + describe '#protected_access_levels_for_dropdowns' do + let(:protected_access_level_dropdown_roles) { :protected_access_level_dropdown_roles } + + before do + allow(helper) + .to receive(:protected_access_level_dropdown_roles) + .and_return(protected_access_level_dropdown_roles) + end + + it 'returns roles for {create,push,merge}_access_levels' do + expect(helper.protected_access_levels_for_dropdowns).to eq( + { + create_access_levels: protected_access_level_dropdown_roles, + push_access_levels: protected_access_level_dropdown_roles, + merge_access_levels: protected_access_level_dropdown_roles + } + ) + end + end + + describe '#protected_access_level_dropdown_roles' do + let(:roles) do + [ + { + id: ::Gitlab::Access::DEVELOPER, + text: 'Developers + Maintainers', + before_divider: true + }, + { + id: ::Gitlab::Access::MAINTAINER, + text: 'Maintainers', + before_divider: true + }, + { + id: ::Gitlab::Access::NO_ACCESS, + text: 'No one', + before_divider: true + } + ] + end + + it 'returns dropdown options for each protected ref access level' do + expect(helper.protected_access_level_dropdown_roles[:roles]).to include(*roles) + end + end +end diff --git a/spec/helpers/registrations_helper_spec.rb b/spec/helpers/registrations_helper_spec.rb index eec87bc8712..b2f9a794cb3 100644 --- a/spec/helpers/registrations_helper_spec.rb +++ b/spec/helpers/registrations_helper_spec.rb @@ -8,20 +8,4 @@ RSpec.describe RegistrationsHelper do expect(helper.signup_username_data_attributes.keys).to include(:min_length, :min_length_message, :max_length, :max_length_message, :qa_selector) end end - - describe '#arkose_labs_challenge_enabled?' do - before do - stub_application_setting( - arkose_labs_private_api_key: nil, - arkose_labs_public_api_key: nil, - arkose_labs_namespace: nil - ) - stub_env('ARKOSE_LABS_PRIVATE_KEY', nil) - stub_env('ARKOSE_LABS_PUBLIC_KEY', nil) - end - - it 'is false' do - expect(helper.arkose_labs_challenge_enabled?).to eq false - end - end end diff --git a/spec/helpers/routing/pseudonymization_helper_spec.rb b/spec/helpers/routing/pseudonymization_helper_spec.rb index eb2cb548f35..784579dc895 100644 --- a/spec/helpers/routing/pseudonymization_helper_spec.rb +++ b/spec/helpers/routing/pseudonymization_helper_spec.rb @@ -26,17 +26,19 @@ RSpec.describe ::Routing::PseudonymizationHelper do context 'with controller for MR' do let(:masked_url) { "http://localhost/namespace#{group.id}/project#{project.id}/-/merge_requests/#{merge_request.id}" } let(:request) do - double(:Request, - path_parameters: { - controller: "projects/merge_requests", - action: "show", - namespace_id: group.name, - project_id: project.name, - id: merge_request.id.to_s - }, - protocol: 'http', - host: 'localhost', - query_string: '') + double( + :Request, + path_parameters: { + controller: "projects/merge_requests", + action: "show", + namespace_id: group.name, + project_id: project.name, + id: merge_request.id.to_s + }, + protocol: 'http', + host: 'localhost', + query_string: '' + ) end before do @@ -49,17 +51,19 @@ RSpec.describe ::Routing::PseudonymizationHelper do context 'with controller for issue' do let(:masked_url) { "http://localhost/namespace#{group.id}/project#{project.id}/-/issues/#{issue.id}" } let(:request) do - double(:Request, - path_parameters: { - controller: "projects/issues", - action: "show", - namespace_id: group.name, - project_id: project.name, - id: issue.id.to_s - }, - protocol: 'http', - host: 'localhost', - query_string: '') + double( + :Request, + path_parameters: { + controller: "projects/issues", + action: "show", + namespace_id: group.name, + project_id: project.name, + id: issue.id.to_s + }, + protocol: 'http', + host: 'localhost', + query_string: '' + ) end before do @@ -74,16 +78,18 @@ RSpec.describe ::Routing::PseudonymizationHelper do let(:group) { subgroup } let(:project) { subproject } let(:request) do - double(:Request, - path_parameters: { - controller: 'projects', - action: 'show', - namespace_id: subgroup.name, - id: subproject.name - }, - protocol: 'http', - host: 'localhost', - query_string: '') + double( + :Request, + path_parameters: { + controller: 'projects', + action: 'show', + namespace_id: subgroup.name, + id: subproject.name + }, + protocol: 'http', + host: 'localhost', + query_string: '' + ) end before do @@ -97,15 +103,17 @@ RSpec.describe ::Routing::PseudonymizationHelper do let(:masked_url) { "http://localhost/groups/namespace#{subgroup.id}/-/shared" } let(:group) { subgroup } let(:request) do - double(:Request, - path_parameters: { - controller: 'groups', - action: 'show', - id: subgroup.name - }, - protocol: 'http', - host: 'localhost', - query_string: '') + double( + :Request, + path_parameters: { + controller: 'groups', + action: 'show', + id: subgroup.name + }, + protocol: 'http', + host: 'localhost', + query_string: '' + ) end before do @@ -118,17 +126,19 @@ RSpec.describe ::Routing::PseudonymizationHelper do context 'with controller for blob with file path' do let(:masked_url) { "http://localhost/namespace#{group.id}/project#{project.id}/-/blob/:repository_path" } let(:request) do - double(:Request, - path_parameters: { - controller: 'projects/blob', - action: 'show', - namespace_id: group.name, - project_id: project.name, - id: 'master/README.md' - }, - protocol: 'http', - host: 'localhost', - query_string: '') + double( + :Request, + path_parameters: { + controller: 'projects/blob', + action: 'show', + namespace_id: group.name, + project_id: project.name, + id: 'master/README.md' + }, + protocol: 'http', + host: 'localhost', + query_string: '' + ) end before do @@ -141,14 +151,16 @@ RSpec.describe ::Routing::PseudonymizationHelper do context 'when assignee_username is present' do let(:masked_url) { "http://localhost/dashboard/issues?assignee_username=masked_assignee_username" } let(:request) do - double(:Request, - path_parameters: { - controller: 'dashboard', - action: 'issues' - }, - protocol: 'http', - host: 'localhost', - query_string: 'assignee_username=root') + double( + :Request, + path_parameters: { + controller: 'dashboard', + action: 'issues' + }, + protocol: 'http', + host: 'localhost', + query_string: 'assignee_username=root' + ) end before do @@ -161,14 +173,16 @@ RSpec.describe ::Routing::PseudonymizationHelper do context 'when author_username is present' do let(:masked_url) { "http://localhost/dashboard/issues?author_username=masked_author_username&scope=all&state=opened" } let(:request) do - double(:Request, - path_parameters: { - controller: 'dashboard', - action: 'issues' - }, - protocol: 'http', - host: 'localhost', - query_string: 'author_username=root&scope=all&state=opened') + double( + :Request, + path_parameters: { + controller: 'dashboard', + action: 'issues' + }, + protocol: 'http', + host: 'localhost', + query_string: 'author_username=root&scope=all&state=opened' + ) end before do @@ -181,14 +195,16 @@ RSpec.describe ::Routing::PseudonymizationHelper do context 'when some query params are not required to be masked' do let(:masked_url) { "http://localhost/dashboard/issues?author_username=masked_author_username&scope=all&state=masked_state&tab=2" } let(:request) do - double(:Request, - path_parameters: { - controller: 'dashboard', - action: 'issues' - }, - protocol: 'http', - host: 'localhost', - query_string: 'author_username=root&scope=all&state=opened&tab=2') + double( + :Request, + path_parameters: { + controller: 'dashboard', + action: 'issues' + }, + protocol: 'http', + host: 'localhost', + query_string: 'author_username=root&scope=all&state=opened&tab=2' + ) end before do @@ -202,14 +218,16 @@ RSpec.describe ::Routing::PseudonymizationHelper do context 'when query string has keys with the same names as path params' do let(:masked_url) { "http://localhost/dashboard/issues?action=masked_action&scope=all&state=opened" } let(:request) do - double(:Request, - path_parameters: { - controller: 'dashboard', - action: 'issues' - }, - protocol: 'http', - host: 'localhost', - query_string: 'action=foobar&scope=all&state=opened') + double( + :Request, + path_parameters: { + controller: 'dashboard', + action: 'issues' + }, + protocol: 'http', + host: 'localhost', + query_string: 'action=foobar&scope=all&state=opened' + ) end before do @@ -223,16 +241,18 @@ RSpec.describe ::Routing::PseudonymizationHelper do describe 'when url has no params to mask' do let(:original_url) { 'http://localhost/-/security/vulnerabilities' } let(:request) do - double(:Request, - path_parameters: { - controller: 'security/vulnerabilities', - action: 'index' - }, - protocol: 'http', - host: 'localhost', - query_string: '', - original_fullpath: '/-/security/vulnerabilities', - original_url: original_url) + double( + :Request, + path_parameters: { + controller: 'security/vulnerabilities', + action: 'index' + }, + protocol: 'http', + host: 'localhost', + query_string: '', + original_fullpath: '/-/security/vulnerabilities', + original_url: original_url + ) end before do @@ -247,15 +267,17 @@ RSpec.describe ::Routing::PseudonymizationHelper do describe 'when it raises exception' do context 'calls error tracking' do let(:request) do - double(:Request, - path_parameters: { - controller: 'dashboard', - action: 'issues' - }, - protocol: 'http', - host: 'localhost', - query_string: 'assignee_username=root', - original_fullpath: '/dashboard/issues?assignee_username=root') + double( + :Request, + path_parameters: { + controller: 'dashboard', + action: 'issues' + }, + protocol: 'http', + host: 'localhost', + query_string: 'assignee_username=root', + original_fullpath: '/dashboard/issues?assignee_username=root' + ) end before do diff --git a/spec/helpers/safe_format_helper_spec.rb b/spec/helpers/safe_format_helper_spec.rb new file mode 100644 index 00000000000..ced48b0c9c1 --- /dev/null +++ b/spec/helpers/safe_format_helper_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SafeFormatHelper, feature_category: :shared do + describe '#safe_format' do + shared_examples 'safe formatting' do |format, args:, result:| + subject { helper.safe_format(format, **args) } + + it { is_expected.to eq(result) } + it { is_expected.to be_html_safe } + end + + it_behaves_like 'safe formatting', '', args: {}, result: '' + it_behaves_like 'safe formatting', 'Foo', args: {}, result: 'Foo' + + it_behaves_like 'safe formatting', 'strong', args: {}, + result: '<b>strong</b>' + + it_behaves_like 'safe formatting', '%{open}strong%{close}', + args: { open: ''.html_safe, close: ''.html_safe }, + result: 'strong' + + it_behaves_like 'safe formatting', '%{open}strong%{close} %{user_input}', + args: { open: ''.html_safe, close: ''.html_safe, + user_input: 'link' }, + result: 'strong <a href="">link</a>' + + context 'when format is marked as html_safe' do + let(:format) { 'strong'.html_safe } + let(:args) { {} } + + it 'raises an error' do + message = 'Argument `format` must not be marked as html_safe!' + + expect { helper.safe_format(format, **args) } + .to raise_error ArgumentError, message + end + end + end +end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index c7afe0bf391..2cea577a852 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -306,6 +306,46 @@ RSpec.describe SearchHelper, feature_category: :global_search do end end + describe 'projects_autocomplete' do + let_it_be(:user) { create(:user, name: "madelein") } + let_it_be(:project_1) { create(:project, name: 'test 1') } + let_it_be(:project_2) { create(:project, name: 'test 2') } + let(:search_term) { 'test' } + + before do + allow(self).to receive(:current_user).and_return(user) + end + + context 'when the user does not have access to projects' do + it 'does not return any results' do + expect(projects_autocomplete(search_term)).to eq([]) + end + end + + context 'when the user has access to one project' do + before do + project_2.add_developer(user) + end + + it 'returns the project' do + expect(projects_autocomplete(search_term).pluck(:id)).to eq([project_2.id]) + end + + context 'when a project namespace matches the search term but the project does not' do + let_it_be(:group) { create(:group, name: 'test group') } + let_it_be(:project_3) { create(:project, name: 'nothing', namespace: group) } + + before do + group.add_owner(user) + end + + it 'returns all projects matching the term' do + expect(projects_autocomplete(search_term).pluck(:id)).to match_array([project_2.id, project_3.id]) + end + end + end + end + describe 'search_entries_info' do using RSpec::Parameterized::TableSyntax @@ -829,6 +869,21 @@ RSpec.describe SearchHelper, feature_category: :global_search do expect(header_search_context[:project_metadata]).to eq(project_metadata) end + context 'feature issues is not available' do + let(:feature_available) { false } + let(:project_metadata) { { mr_path: project_merge_requests_path(project) } } + + before do + allow(project).to receive(:feature_available?).and_call_original + allow(project).to receive(:feature_available?).with(:issues, current_user).and_return(feature_available) + end + + it 'adds the :project and :project-metadata correctly to hash' do + expect(header_search_context[:project]).to eq({ id: project.id, name: project.name }) + expect(header_search_context[:project_metadata]).to eq(project_metadata) + end + end + context 'with scope' do let(:scope) { 'issues' } diff --git a/spec/helpers/sessions_helper_spec.rb b/spec/helpers/sessions_helper_spec.rb index c7b8225b866..5a46a20ce1a 100644 --- a/spec/helpers/sessions_helper_spec.rb +++ b/spec/helpers/sessions_helper_spec.rb @@ -52,7 +52,7 @@ RSpec.describe SessionsHelper do end describe '#send_rate_limited?' do - let_it_be(:user) { build(:user) } + let(:user) { build_stubbed(:user) } subject { helper.send_rate_limited?(user) } @@ -77,30 +77,34 @@ RSpec.describe SessionsHelper do end describe '#obfuscated_email' do + let(:email) { 'mail@example.com' } + subject { helper.obfuscated_email(email) } - context 'when an email address is normal length' do - let(:email) { 'alex@gitlab.com' } + it 'delegates to Gitlab::Utils::Email.obfuscated_email' do + expect(Gitlab::Utils::Email).to receive(:obfuscated_email).with(email).and_call_original - it { is_expected.to eq('al**@g*****.com') } + expect(subject).to eq('ma**@e******.com') end + end - context 'when an email address contains multiple top level domains' do - let(:email) { 'alex@gl.co.uk' } - - it { is_expected.to eq('al**@g****.uk') } - end + describe '#remember_me_enabled?' do + subject { helper.remember_me_enabled? } - context 'when an email address is very short' do - let(:email) { 'a@b.c' } + context 'when application setting is enabled' do + before do + stub_application_setting(remember_me_enabled: true) + end - it { is_expected.to eq('a@b.c') } + it { is_expected.to be true } end - context 'when an email address is even shorter' do - let(:email) { 'a@b' } + context 'when application setting is disabled' do + before do + stub_application_setting(remember_me_enabled: false) + end - it { is_expected.to eq('a@b') } + it { is_expected.to be false } end end end diff --git a/spec/helpers/sidebars_helper_spec.rb b/spec/helpers/sidebars_helper_spec.rb index 672c2ef7589..6648663b634 100644 --- a/spec/helpers/sidebars_helper_spec.rb +++ b/spec/helpers/sidebars_helper_spec.rb @@ -62,39 +62,154 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do end describe '#super_sidebar_context' do - let(:user) { build(:user) } - let(:group) { build(:group) } + include_context 'custom session' + + let_it_be(:user) { build(:user) } + let_it_be(:group) { build(:group) } + let_it_be(:panel) { {} } + let_it_be(:panel_type) { 'project' } + let(:project) { nil } + let(:current_user_mode) { Gitlab::Auth::CurrentUserMode.new(user) } - subject { helper.super_sidebar_context(user, group: group, project: nil) } + subject do + helper.super_sidebar_context(user, group: group, project: project, panel: panel, panel_type: panel_type) + end before do + allow(Time).to receive(:now).and_return(Time.utc(2021, 1, 1)) allow(helper).to receive(:current_user) { user } - Rails.cache.write(['users', user.id, 'assigned_open_issues_count'], 1) - Rails.cache.write(['users', user.id, 'assigned_open_merge_requests_count'], 4) - Rails.cache.write(['users', user.id, 'review_requested_open_merge_requests_count'], 0) - Rails.cache.write(['users', user.id, 'todos_pending_count'], 3) - Rails.cache.write(['users', user.id, 'total_merge_requests_count'], 4) + allow(helper).to receive(:can?).and_return(true) + allow(helper).to receive(:session).and_return(session) + allow(helper).to receive(:header_search_context).and_return({ some: "search data" }) + allow(helper).to receive(:current_user_mode).and_return(current_user_mode) + allow(panel).to receive(:super_sidebar_menu_items).and_return(nil) + allow(panel).to receive(:super_sidebar_context_header).and_return(nil) + allow(user).to receive(:assigned_open_issues_count).and_return(1) + allow(user).to receive(:assigned_open_merge_requests_count).and_return(4) + allow(user).to receive(:review_requested_open_merge_requests_count).and_return(0) + allow(user).to receive(:todos_pending_count).and_return(3) + allow(user).to receive(:pinned_nav_items).and_return({ panel_type => %w[foo bar], 'another_panel' => %w[baz] }) end it 'returns sidebar values from user', :use_clean_rails_memory_store_caching do expect(subject).to include({ + current_context_header: nil, + current_menu_items: nil, name: user.name, username: user.username, avatar_url: user.avatar_url, - assigned_open_issues_count: 1, - todos_pending_count: 3, + has_link_to_profile: helper.current_user_menu?(:profile), + link_to_profile: user_url(user), + status: { + can_update: helper.can?(user, :update_user_status, user), + busy: user.status&.busy?, + customized: user.status&.customized?, + availability: user.status&.availability.to_s, + emoji: user.status&.emoji, + message: user.status&.message_html&.html_safe, + clear_after: nil + }, + settings: { + has_settings: helper.current_user_menu?(:settings), + profile_path: profile_path, + profile_preferences_path: profile_preferences_path + }, + user_counts: { + assigned_issues: 1, + assigned_merge_requests: 4, + review_requested_merge_requests: 0, + todos: 3, + last_update: 1609459200000 + }, + can_sign_out: helper.current_user_menu?(:sign_out), + sign_out_link: destroy_user_session_path, issues_dashboard_path: issues_dashboard_path(assignee_username: user.username), - total_merge_requests_count: 4, + todos_dashboard_path: dashboard_todos_path, + projects_path: dashboard_projects_path, + groups_path: dashboard_groups_path, support_path: helper.support_url, display_whats_new: helper.display_whats_new?, whats_new_most_recent_release_items_count: helper.whats_new_most_recent_release_items_count, whats_new_version_digest: helper.whats_new_version_digest, show_version_check: helper.show_version_check?, gitlab_version: Gitlab.version_info, - gitlab_version_check: helper.gitlab_version_check + gitlab_version_check: helper.gitlab_version_check, + gitlab_com_but_not_canary: Gitlab.com_but_not_canary?, + gitlab_com_and_canary: Gitlab.com_and_canary?, + canary_toggle_com_url: Gitlab::Saas.canary_toggle_com_url, + search: { + search_path: search_path, + issues_path: issues_dashboard_path, + mr_path: merge_requests_dashboard_path, + autocomplete_path: search_autocomplete_path, + search_context: helper.header_search_context + }, + pinned_items: %w[foo bar], + panel_type: panel_type, + update_pins_url: pins_url, + shortcut_links: [ + { + title: _('Milestones'), + href: dashboard_milestones_path, + css_class: 'dashboard-shortcuts-milestones' + }, + { + title: _('Snippets'), + href: dashboard_snippets_path, + css_class: 'dashboard-shortcuts-snippets' + }, + { + title: _('Activity'), + href: activity_dashboard_path, + css_class: 'dashboard-shortcuts-activity' + } + ] }) end + describe "shortcut links" do + let(:global_shortcut_links) do + [ + { + title: _('Milestones'), + href: dashboard_milestones_path, + css_class: 'dashboard-shortcuts-milestones' + }, + { + title: _('Snippets'), + href: dashboard_snippets_path, + css_class: 'dashboard-shortcuts-snippets' + }, + { + title: _('Activity'), + href: activity_dashboard_path, + css_class: 'dashboard-shortcuts-activity' + } + ] + end + + it 'returns global shortcut links' do + expect(subject[:shortcut_links]).to eq(global_shortcut_links) + end + + context 'in a project' do + # rubocop: disable RSpec/FactoryBot/AvoidCreate + let_it_be(:project) { create(:project) } + # rubocop: enable RSpec/FactoryBot/AvoidCreate + + it 'returns project-specific shortcut links' do + expect(subject[:shortcut_links]).to eq([ + *global_shortcut_links, + { + title: _('Create a new issue'), + href: new_project_issue_path(project), + css_class: 'shortcuts-new-issue' + } + ]) + end + end + end + it 'returns "Merge requests" menu', :use_clean_rails_memory_store_caching do expect(subject[:merge_request_menu]).to eq([ { @@ -103,12 +218,26 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do { text: _('Assigned'), href: merge_requests_dashboard_path(assignee_username: user.username), - count: 4 + count: 4, + userCount: 'assigned_merge_requests', + extraAttrs: { + 'data-track-action': 'click_link', + 'data-track-label': 'merge_requests_assigned', + 'data-track-property': 'nav_core_menu', + class: 'dashboard-shortcuts-merge_requests' + } }, { text: _('Review requests'), href: merge_requests_dashboard_path(reviewer_username: user.username), - count: 0 + count: 0, + userCount: 'review_requested_merge_requests', + extraAttrs: { + 'data-track-action': 'click_link', + 'data-track-label': 'merge_requests_to_review', + 'data-track-property': 'nav_core_menu', + class: 'dashboard-shortcuts-review_requests' + } } ] } @@ -116,19 +245,45 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do end it 'returns "Create new" menu groups without headers', :use_clean_rails_memory_store_caching do + extra_attrs = ->(id) { + { + "data-track-label": id, + "data-track-action": "click_link", + "data-track-property": "nav_create_menu", + "data-qa-selector": 'create_menu_item', + "data-qa-create-menu-item": id + } + } + expect(subject[:create_new_menu_groups]).to eq([ { name: "", items: [ - { href: "/projects/new", text: "New project/repository" }, - { href: "/groups/new", text: "New group" }, - { href: "/-/snippets/new", text: "New snippet" } + { href: "/projects/new", text: "New project/repository", + component: nil, + extraAttrs: extra_attrs.call("general_new_project") }, + { href: "/groups/new", text: "New group", + component: nil, + extraAttrs: extra_attrs.call("general_new_group") }, + { href: "/-/snippets/new", text: "New snippet", + component: nil, + extraAttrs: extra_attrs.call("general_new_snippet") } ] } ]) end it 'returns "Create new" menu groups with headers', :use_clean_rails_memory_store_caching do + extra_attrs = ->(id) { + { + "data-track-label": id, + "data-track-action": "click_link", + "data-track-property": "nav_create_menu", + "data-qa-selector": 'create_menu_item', + "data-qa-create-menu-item": id + } + } + allow(group).to receive(:persisted?).and_return(true) allow(helper).to receive(:can?).and_return(true) @@ -136,20 +291,241 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do a_hash_including( name: "In this group", items: array_including( - { href: "/projects/new", text: "New project/repository" }, - { href: "/groups/new#create-group-pane", text: "New subgroup" }, - { href: "/groups/#{group.full_path}/-/group_members", text: "Invite members" } + { href: "/projects/new", text: "New project/repository", + component: nil, + extraAttrs: extra_attrs.call("new_project") }, + { href: "/groups/new#create-group-pane", text: "New subgroup", + component: nil, + extraAttrs: extra_attrs.call("new_subgroup") }, + { href: nil, text: "Invite members", + component: 'invite_members', + extraAttrs: extra_attrs.call("invite") } ) ), a_hash_including( name: "In GitLab", items: array_including( - { href: "/projects/new", text: "New project/repository" }, - { href: "/groups/new", text: "New group" }, - { href: "/-/snippets/new", text: "New snippet" } + { href: "/projects/new", text: "New project/repository", + component: nil, + extraAttrs: extra_attrs.call("general_new_project") }, + { href: "/groups/new", text: "New group", + component: nil, + extraAttrs: extra_attrs.call("general_new_group") }, + { href: "/-/snippets/new", text: "New snippet", + component: nil, + extraAttrs: extra_attrs.call("general_new_snippet") } ) ) ) end + + describe 'current context' do + context 'when current context is a project' do + let_it_be(:project) { build(:project) } + + subject do + helper.super_sidebar_context(user, group: nil, project: project, panel: panel, panel_type: panel_type) + end + + before do + allow(project).to receive(:persisted?).and_return(true) + end + + it 'returns project context' do + expect(subject[:current_context]).to eq({ + namespace: 'projects', + item: { + id: project.id, + avatarUrl: project.avatar_url, + name: project.name, + namespace: project.full_name, + webUrl: project_path(project) + } + }) + end + end + + context 'when current context is a group' do + subject do + helper.super_sidebar_context(user, group: group, project: nil, panel: panel, panel_type: panel_type) + end + + before do + allow(group).to receive(:persisted?).and_return(true) + end + + it 'returns group context' do + expect(subject[:current_context]).to eq({ + namespace: 'groups', + item: { + id: group.id, + avatarUrl: group.avatar_url, + name: group.name, + namespace: group.full_name, + webUrl: group_path(group) + } + }) + end + end + + context 'when current context is not tracked' do + subject do + helper.super_sidebar_context(user, group: nil, project: nil, panel: panel, panel_type: panel_type) + end + + it 'returns no context' do + expect(subject[:current_context]).to eq({}) + end + end + end + + describe 'context switcher persistent links' do + let_it_be(:public_link) do + [ + { title: s_('Navigation|Your work'), link: '/', icon: 'work' }, + { title: s_('Navigation|Explore'), link: '/explore', icon: 'compass' } + ] + end + + let_it_be(:admin_area_link) do + { title: s_('Navigation|Admin Area'), link: '/admin', icon: 'admin' } + end + + let_it_be(:enter_admin_mode_link) do + { title: s_('Navigation|Enter admin mode'), link: '/admin/session/new', icon: 'lock' } + end + + let_it_be(:leave_admin_mode_link) do + { title: s_('Navigation|Leave admin mode'), link: '/admin/session/destroy', icon: 'lock-open', + data_method: 'post' } + end + + subject do + helper.super_sidebar_context(user, group: nil, project: nil, panel: panel, panel_type: panel_type) + end + + context 'when user is not an admin' do + it 'returns only the public links' do + expect(subject[:context_switcher_links]).to eq(public_link) + end + end + + context 'when user is an admin' do + before do + allow(user).to receive(:admin?).and_return(true) + end + + context 'when application setting :admin_mode is enabled' do + before do + stub_application_setting(admin_mode: true) + end + + context 'when admin mode is on' do + before do + current_user_mode.request_admin_mode! + current_user_mode.enable_admin_mode!(password: user.password) + end + + it 'returns public links, admin area and leave admin mode links' do + expect(subject[:context_switcher_links]).to eq([ + *public_link, + admin_area_link, + leave_admin_mode_link + ]) + end + end + + context 'when admin mode is off' do + it 'returns public links and enter admin mode link' do + expect(subject[:context_switcher_links]).to eq([ + *public_link, + enter_admin_mode_link + ]) + end + end + end + + context 'when application setting :admin_mode is disabled' do + before do + stub_application_setting(admin_mode: false) + end + + it 'returns public links and admin area link' do + expect(subject[:context_switcher_links]).to eq([ + *public_link, + admin_area_link + ]) + end + end + end + end + + describe 'impersonation data' do + it 'sets is_impersonating to `false` when not impersonating' do + expect(subject[:is_impersonating]).to be(false) + end + + it 'passes the stop_impersonation_path property' do + expect(subject[:stop_impersonation_path]).to eq(admin_impersonation_path) + end + + describe 'when impersonating' do + it 'sets is_impersonating to `true`' do + expect(helper).to receive(:session).and_return({ impersonator_id: 1 }) + expect(subject[:is_impersonating]).to be(true) + end + end + end + end + + describe '#super_sidebar_nav_panel' do + let(:user) { build(:user) } + let(:group) { build(:group) } + let(:project) { build(:project) } + + before do + allow(helper).to receive(:project_sidebar_context_data).and_return( + { current_user: nil, container: project, can_view_pipeline_editor: false, learn_gitlab_enabled: false }) + allow(helper).to receive(:group_sidebar_context_data).and_return( + { current_user: nil, container: group, show_discover_group_security: false }) + + allow(group).to receive(:to_global_id).and_return(5) + Rails.cache.write(['users', user.id, 'assigned_open_issues_count'], 1) + Rails.cache.write(['users', user.id, 'assigned_open_merge_requests_count'], 4) + Rails.cache.write(['users', user.id, 'review_requested_open_merge_requests_count'], 0) + Rails.cache.write(['users', user.id, 'todos_pending_count'], 3) + end + + it 'returns Project Panel for project nav' do + expect(helper.super_sidebar_nav_panel(nav: 'project')).to be_a(Sidebars::Projects::SuperSidebarPanel) + end + + it 'returns Group Panel for group nav' do + expect(helper.super_sidebar_nav_panel(nav: 'group')).to be_a(Sidebars::Groups::SuperSidebarPanel) + end + + it 'returns User Settings Panel for profile nav' do + expect(helper.super_sidebar_nav_panel(nav: 'profile')).to be_a(Sidebars::UserSettings::Panel) + end + + it 'returns User profile Panel for user profile nav' do + expect(helper.super_sidebar_nav_panel(nav: 'user_profile')).to be_a(Sidebars::UserProfile::Panel) + end + + it 'returns Admin Panel for admin nav' do + expect(helper.super_sidebar_nav_panel(nav: 'admin')).to be_a(Sidebars::Admin::Panel) + end + + it 'returns "Your Work" Panel for your_work nav', :use_clean_rails_memory_store_caching do + expect(helper.super_sidebar_nav_panel(nav: 'your_work', user: user)).to be_a(Sidebars::YourWork::Panel) + end + + it 'returns Search Panel for search nav' do + expect(helper.super_sidebar_nav_panel(nav: 'search', user: user)).to be_a(Sidebars::Search::Panel) + end + + it 'returns "Your Work" Panel as a fallback', :use_clean_rails_memory_store_caching do + expect(helper.super_sidebar_nav_panel(user: user)).to be_a(Sidebars::YourWork::Panel) + end end end diff --git a/spec/helpers/sorting_helper_spec.rb b/spec/helpers/sorting_helper_spec.rb index d561b08efac..d625b46e286 100644 --- a/spec/helpers/sorting_helper_spec.rb +++ b/spec/helpers/sorting_helper_spec.rb @@ -10,6 +10,60 @@ RSpec.describe SortingHelper do allow(self).to receive(:request).and_return(double(path: 'http://test.com', query_parameters: { label_name: option })) end + describe '#issuable_sort_options' do + let(:viewing_issues) { false } + let(:viewing_merge_requests) { false } + let(:params) { {} } + + subject(:options) { helper.issuable_sort_options(viewing_issues, viewing_merge_requests) } + + before do + allow(helper).to receive(:params).and_return(params) + end + + shared_examples 'with merged date option' do + it 'adds merged date option' do + expect(options).to include( + a_hash_including( + value: 'merged_at', + text: 'Merged date' + ) + ) + end + end + + shared_examples 'without merged date option' do + it 'does not set merged date option' do + expect(options).not_to include( + a_hash_including( + value: 'merged_at', + text: 'Merged date' + ) + ) + end + end + + it_behaves_like 'without merged date option' + + context 'when viewing_merge_requests is true' do + let(:viewing_merge_requests) { true } + + it_behaves_like 'without merged date option' + + context 'when state param is all' do + let(:params) { { state: 'all' } } + + it_behaves_like 'with merged date option' + end + + context 'when state param is merged' do + let(:params) { { state: 'merged' } } + + it_behaves_like 'with merged date option' + end + end + end + describe '#admin_users_sort_options' do it 'returns correct link attributes in array' do options = admin_users_sort_options(filter: 'filter', search_query: 'search') diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb index 6c0f1034d65..d62da2ca714 100644 --- a/spec/helpers/storage_helper_spec.rb +++ b/spec/helpers/storage_helper_spec.rb @@ -24,18 +24,22 @@ RSpec.describe StorageHelper do describe "#storage_counters_details" do let_it_be(:namespace) { create(:namespace) } let_it_be(:project) do - create(:project, - namespace: namespace, - statistics: build(:project_statistics, - namespace: namespace, - repository_size: 10.kilobytes, - wiki_size: 10.bytes, - lfs_objects_size: 20.gigabytes, - build_artifacts_size: 30.megabytes, - pipeline_artifacts_size: 11.megabytes, - snippets_size: 40.megabytes, - packages_size: 12.megabytes, - uploads_size: 15.megabytes)) + create( + :project, + namespace: namespace, + statistics: build( + :project_statistics, + namespace: namespace, + repository_size: 10.kilobytes, + wiki_size: 10.bytes, + lfs_objects_size: 20.gigabytes, + build_artifacts_size: 30.megabytes, + pipeline_artifacts_size: 11.megabytes, + snippets_size: 40.megabytes, + packages_size: 12.megabytes, + uploads_size: 15.megabytes + ) + ) end let(:message) { 'Repository: 10 KB / Wikis: 10 Bytes / Build Artifacts: 30 MB / Pipeline Artifacts: 11 MB / LFS: 20 GB / Snippets: 40 MB / Packages: 12 MB / Uploads: 15 MB' } diff --git a/spec/helpers/todos_helper_spec.rb b/spec/helpers/todos_helper_spec.rb index 26951b0c1e7..9cbcca69dc8 100644 --- a/spec/helpers/todos_helper_spec.rb +++ b/spec/helpers/todos_helper_spec.rb @@ -9,20 +9,21 @@ RSpec.describe TodosHelper do let_it_be(:issue) { create(:issue, title: 'Issue 1', project: project) } let_it_be(:design) { create(:design, issue: issue) } let_it_be(:note) do - create(:note, - project: issue.project, - note: 'I am note, hear me roar') + create(:note, project: issue.project, note: 'I am note, hear me roar') end let_it_be(:group) { create(:group, :public, name: 'Group 1') } let_it_be(:design_todo) do - create(:todo, :mentioned, - user: user, - project: project, - target: design, - author: author, - note: note) + create( + :todo, + :mentioned, + user: user, + project: project, + target: design, + author: author, + note: note + ) end let_it_be(:alert_todo) do @@ -93,11 +94,14 @@ RSpec.describe TodosHelper do context 'when given a non-design todo' do let(:todo) do - build_stubbed(:todo, :assigned, - user: user, - project: issue.project, - target: issue, - author: author) + build_stubbed( + :todo, + :assigned, + user: user, + project: issue.project, + target: issue, + author: author + ) end it 'returns the title' do @@ -135,22 +139,10 @@ RSpec.describe TodosHelper do context 'when given a task' do let(:todo) { task_todo } - context 'when the use_iid_in_work_items_path feature flag is disabled' do - before do - stub_feature_flags(use_iid_in_work_items_path: false) - end - - it 'responds with an appropriate path' do - path = helper.todo_target_path(todo) - - expect(path).to eq("/#{todo.project.full_path}/-/work_items/#{todo.target.id}") - end - end - it 'responds with an appropriate path using iid' do path = helper.todo_target_path(todo) - expect(path).to eq("/#{todo.project.full_path}/-/work_items/#{todo.target.iid}?iid_path=true") + expect(path).to eq("/#{todo.project.full_path}/-/work_items/#{todo.target.iid}") end end @@ -166,11 +158,13 @@ RSpec.describe TodosHelper do context 'when a user requests access to group' do let_it_be(:group_access_request_todo) do - create(:todo, - target_id: group.id, - target_type: group.class.polymorphic_name, - group: group, - action: Todo::MEMBER_ACCESS_REQUESTED) + create( + :todo, + target_id: group.id, + target_type: group.class.polymorphic_name, + group: group, + action: Todo::MEMBER_ACCESS_REQUESTED + ) end it 'responds with access requests tab' do @@ -307,7 +301,7 @@ RSpec.describe TodosHelper do end describe '#no_todos_messages' do - context 'when getting todos messsages' do + context 'when getting todos messages' do it 'return these sentences' do expected_sentences = [ s_('Todos|Good job! Looks like you don\'t have anything left on your To-Do List'), diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb index c40284ee933..01dacf5fcad 100644 --- a/spec/helpers/tree_helper_spec.rb +++ b/spec/helpers/tree_helper_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' RSpec.describe TreeHelper do + include Devise::Test::ControllerHelpers let_it_be(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:sha) { 'c1c67abbaf91f624347bb3ae96eabe3a1b742478' } diff --git a/spec/helpers/users/callouts_helper_spec.rb b/spec/helpers/users/callouts_helper_spec.rb index 4cb179e4f60..cb724816daf 100644 --- a/spec/helpers/users/callouts_helper_spec.rb +++ b/spec/helpers/users/callouts_helper_spec.rb @@ -165,7 +165,27 @@ RSpec.describe Users::CalloutsHelper do end end - describe '#web_hook_disabled_dismissed?' do + describe '.show_pages_menu_callout?' do + subject { helper.show_pages_menu_callout? } + + before do + allow(helper).to receive(:user_dismissed?).with(described_class::PAGES_MOVED_CALLOUT) { dismissed } + end + + context 'when user has not dismissed' do + let(:dismissed) { false } + + it { is_expected.to be true } + end + + context 'when user dismissed' do + let(:dismissed) { true } + + it { is_expected.to be false } + end + end + + describe '#web_hook_disabled_dismissed?', feature_category: :integrations do context 'without a project' do it 'is false' do expect(helper).not_to be_web_hook_disabled_dismissed(nil) @@ -174,50 +194,12 @@ RSpec.describe Users::CalloutsHelper do context 'with a project' do let_it_be(:project) { create(:project) } + let(:factory) { :project_callout } + let(:container_key) { :project } + let(:container) { project } + let(:key) { "web_hooks:last_failure:project-#{project.id}" } - context 'the web-hook failure callout has never been dismissed' do - it 'is false' do - expect(helper).not_to be_web_hook_disabled_dismissed(project) - end - end - - context 'the web-hook failure callout has been dismissed', :freeze_time do - before do - create(:project_callout, - feature_name: described_class::WEB_HOOK_DISABLED, - user: user, - project: project, - dismissed_at: 1.week.ago) - end - - it 'is true' do - expect(helper).to be_web_hook_disabled_dismissed(project) - end - - context 'when there was an older failure', :clean_gitlab_redis_shared_state do - let(:key) { "web_hooks:last_failure:project-#{project.id}" } - - before do - Gitlab::Redis::SharedState.with { |r| r.set(key, 1.month.ago.iso8601) } - end - - it 'is true' do - expect(helper).to be_web_hook_disabled_dismissed(project) - end - end - - context 'when there has been a more recent failure', :clean_gitlab_redis_shared_state do - let(:key) { "web_hooks:last_failure:project-#{project.id}" } - - before do - Gitlab::Redis::SharedState.with { |r| r.set(key, 1.day.ago.iso8601) } - end - - it 'is false' do - expect(helper).not_to be_web_hook_disabled_dismissed(project) - end - end - end + include_examples 'CalloutsHelper#web_hook_disabled_dismissed shared examples' end end end diff --git a/spec/helpers/users/group_callouts_helper_spec.rb b/spec/helpers/users/group_callouts_helper_spec.rb index da67c4921b3..c6679069c49 100644 --- a/spec/helpers/users/group_callouts_helper_spec.rb +++ b/spec/helpers/users/group_callouts_helper_spec.rb @@ -70,10 +70,12 @@ RSpec.describe Users::GroupCalloutsHelper do context 'when the invite_members_banner has been dismissed' do before do - create(:group_callout, - user: user, - group: group, - feature_name: described_class::INVITE_MEMBERS_BANNER) + create( + :group_callout, + user: user, + group: group, + feature_name: described_class::INVITE_MEMBERS_BANNER + ) end it { is_expected.to eq(false) } diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb index c2c78be6a0f..f26c37a5ff2 100644 --- a/spec/helpers/users_helper_spec.rb +++ b/spec/helpers/users_helper_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe UsersHelper do include TermsHelper - let(:user) { create(:user) } + let_it_be(:user) { create(:user, timezone: ActiveSupport::TimeZone::MAPPING['UTC']) } def filter_ee_badges(badges) badges.reject { |badge| badge[:text] == 'Is using seat' } @@ -37,6 +37,35 @@ RSpec.describe UsersHelper do end end + describe '#user_clear_status_at' do + context 'when status exists' do + context 'with clear_status_at set' do + it 'has the correct iso formatted date', time_travel_to: '2020-01-01 00:00:00 +0000' do + clear_status_at = 1.day.from_now + status = build_stubbed(:user_status, clear_status_at: clear_status_at) + + expect(user_clear_status_at(status.user)).to eq('2020-01-02T00:00:00Z') + end + end + + context 'without clear_status_at set' do + it 'returns nil' do + status = build_stubbed(:user_status, clear_status_at: nil) + + expect(user_clear_status_at(status.user)).to be_nil + end + end + end + + context 'without status' do + it 'returns nil' do + user = build_stubbed(:user) + + expect(user_clear_status_at(user)).to be_nil + end + end + end + describe '#profile_tabs' do subject(:tabs) { helper.profile_tabs } @@ -94,10 +123,6 @@ RSpec.describe UsersHelper do allow(helper).to receive(:can?).and_return(false) end - after do - expect(items).not_to include(:start_trial) - end - it 'includes all default items' do expect(items).to include(:help, :sign_out) end @@ -468,4 +493,72 @@ RSpec.describe UsersHelper do expect(data[:paths]).to match_schema('entities/admin_users_data_attributes_paths') end end + + describe '#user_profile_tabs_app_data' do + before do + allow(helper).to receive(:user_calendar_path).with(user, :json).and_return('/users/root/calendar.json') + allow(user).to receive_message_chain(:followers, :count).and_return(2) + allow(user).to receive_message_chain(:followees, :count).and_return(3) + end + + it 'returns expected hash' do + expect(helper.user_profile_tabs_app_data(user)).to eq({ + followees: 3, + followers: 2, + user_calendar_path: '/users/root/calendar.json', + utc_offset: 0, + user_id: user.id + }) + end + end + + describe '#load_max_project_member_accesses' do + let_it_be(:projects) { create_list(:project, 3) } + + before(:all) do + projects.first.add_developer(user) + end + + context 'without current_user' do + before do + allow(helper).to receive(:current_user).and_return(nil) + end + + it 'executes no queries' do + sample = ActiveRecord::QueryRecorder.new do + helper.load_max_project_member_accesses(projects) + end + + expect(sample).not_to exceed_query_limit(0) + end + end + + context 'when current_user is present', :request_store do + before do + allow(helper).to receive(:current_user).and_return(user) + end + + it 'preloads ProjectPolicy#lookup_access_level! and UsersHelper#max_member_project_member_access for current_user in two queries', :aggregate_failures do + preload_queries = ActiveRecord::QueryRecorder.new do + helper.load_max_project_member_accesses(projects) + end + + helper_queries = ActiveRecord::QueryRecorder.new do + projects.each do |project| + helper.max_project_member_access(project) + end + end + + access_queries = ActiveRecord::QueryRecorder.new do + projects.each do |project| + user.can?(:read_code, project) + end + end + + expect(preload_queries).not_to exceed_query_limit(2) + expect(helper_queries).not_to exceed_query_limit(0) + expect(access_queries).not_to exceed_query_limit(0) + end + end + end end diff --git a/spec/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb index 1c8eacf088a..ce5aade2b1c 100644 --- a/spec/helpers/version_check_helper_spec.rb +++ b/spec/helpers/version_check_helper_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe VersionCheckHelper do + include StubVersion + let_it_be(:user) { create(:user) } describe '#show_version_check?' do @@ -82,4 +84,32 @@ RSpec.describe VersionCheckHelper do end end end + + describe '#link_to_version' do + let(:release_url) { 'https://gitlab.com/gitlab-org/gitlab-foss/-/tags/deadbeef' } + + before do + allow(Gitlab::Source).to receive(:release_url).and_return(release_url) + end + + context 'for a pre-release' do + before do + stub_version('8.0.2-pre', 'deadbeef') + end + + it 'links to commit sha' do + expect(helper.link_to_version).to eq("8.0.2-pre deadbeef") + end + end + + context 'for a normal release' do + before do + stub_version('8.0.2-ee', 'deadbeef') + end + + it 'links to version tag' do + expect(helper.link_to_version).to include("v8.0.2-ee") + end + end + end end diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb index 2aac0cae0c6..8f37bf29a4b 100644 --- a/spec/helpers/visibility_level_helper_spec.rb +++ b/spec/helpers/visibility_level_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe VisibilityLevelHelper do +RSpec.describe VisibilityLevelHelper, feature_category: :system_access do include ProjectForksHelper let(:project) { build(:project) } @@ -78,6 +78,23 @@ RSpec.describe VisibilityLevelHelper do expect(descriptions.uniq.size).to eq(descriptions.size) expect(descriptions).to all match /group/i end + + it 'returns default description for public group' do + expect(descriptions[2]).to eq('The group and any public projects can be viewed without any authentication.') + end + + context 'when application setting `should_check_namespace_plan` is true', if: Gitlab.ee? do + let(:group) { create(:group) } + let(:public_option_description) { visibility_level_description(Gitlab::VisibilityLevel::PUBLIC, group) } + + before do + allow(Gitlab::CurrentSettings.current_application_settings).to receive(:should_check_namespace_plan?) { true } + end + + it 'returns updated description for public visibility option in group general settings' do + expect(public_option_description).to match /^The group, any public projects, and any of their members, issues, and merge requests can be viewed without authentication./ + end + end end end @@ -161,8 +178,10 @@ RSpec.describe VisibilityLevelHelper do end before do - stub_application_setting(restricted_visibility_levels: restricted_levels, - default_project_visibility: global_default_level) + stub_application_setting( + restricted_visibility_levels: restricted_levels, + default_project_visibility: global_default_level + ) end with_them do diff --git a/spec/helpers/work_items_helper_spec.rb b/spec/helpers/work_items_helper_spec.rb new file mode 100644 index 00000000000..4e1eca3d411 --- /dev/null +++ b/spec/helpers/work_items_helper_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe WorkItemsHelper, feature_category: :team_planning do + describe '#work_items_index_data' do + subject(:work_items_index_data) { helper.work_items_index_data(project) } + + let_it_be(:project) { build(:project) } + + it 'returns the expected data properties' do + expect(work_items_index_data).to include( + { + full_path: project.full_path, + issues_list_path: project_issues_path(project), + register_path: new_user_registration_path(redirect_to_referer: 'yes'), + sign_in_path: user_session_path(redirect_to_referer: 'yes'), + new_comment_template_path: profile_comment_templates_path, + report_abuse_path: add_category_abuse_reports_path + } + ) + end + end +end -- cgit v1.2.3