diff options
Diffstat (limited to 'spec/helpers')
27 files changed, 841 insertions, 223 deletions
diff --git a/spec/helpers/admin/application_settings/settings_helper_spec.rb b/spec/helpers/admin/application_settings/settings_helper_spec.rb index b008f52c0eb..9981e0d12bd 100644 --- a/spec/helpers/admin/application_settings/settings_helper_spec.rb +++ b/spec/helpers/admin/application_settings/settings_helper_spec.rb @@ -31,24 +31,4 @@ RSpec.describe Admin::ApplicationSettings::SettingsHelper do }) end end - - describe 'Code Suggestions for Self-Managed instances', feature_category: :code_suggestions do - describe '#code_suggestions_description' do - subject { helper.code_suggestions_description } - - it { is_expected.to include 'https://docs.gitlab.com/ee/user/project/repository/code_suggestions.html' } - end - - describe '#code_suggestions_token_explanation' do - subject { helper.code_suggestions_token_explanation } - - it { is_expected.to include 'https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#create-a-personal-access-token' } - end - - describe '#code_suggestions_agreement' do - subject { helper.code_suggestions_agreement } - - it { is_expected.to include 'https://about.gitlab.com/handbook/legal/testing-agreement/' } - end - end end diff --git a/spec/helpers/broadcast_messages_helper_spec.rb b/spec/helpers/admin/broadcast_messages_helper_spec.rb index 05e745e249e..434b79d5271 100644 --- a/spec/helpers/broadcast_messages_helper_spec.rb +++ b/spec/helpers/admin/broadcast_messages_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do +RSpec.describe Admin::BroadcastMessagesHelper, feature_category: :onboarding do include Gitlab::Routing.url_helpers let_it_be(:user) { create(:user) } @@ -102,7 +102,7 @@ RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do end describe '#broadcast_message' do - let(:current_broadcast_message) { BroadcastMessage.new(message: 'Current Message') } + let(:current_broadcast_message) { System::BroadcastMessage.new(message: 'Current Message') } it 'returns nil when no current message' do expect(helper.broadcast_message(nil)).to be_nil @@ -133,6 +133,36 @@ RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do end end + describe '#render_broadcast_message' do + context 'when message is banner' do + let_it_be(:broadcast_message) do + System::BroadcastMessage.new(message: 'Current Message', broadcast_type: :banner) + end.freeze + + it 'renders broadcast message' do + expect(helper.render_broadcast_message(broadcast_message)).to eq("<p>Current Message</p>") + end + end + + context 'when message is notification' do + let_it_be(:broadcast_message) do + System::BroadcastMessage.new(message: 'Current Message', broadcast_type: :notification) + end.freeze + + it 'renders broadcast message' do + expect(helper.render_broadcast_message(broadcast_message)).to eq("<p>Current Message</p>") + end + end + end + + describe '#target_access_levels_display' do + let_it_be(:access_levels) { [Gitlab::Access::REPORTER, Gitlab::Access::DEVELOPER] }.freeze + + it 'joins access levels' do + expect(helper.target_access_levels_display(access_levels)).to eq("Reporter, Developer") + end + end + describe '#admin_broadcast_messages_data' do let(:starts_at) { 1.day.ago } let(:ends_at) { 1.day.from_now } diff --git a/spec/helpers/admin/deploy_key_helper_spec.rb b/spec/helpers/admin/deploy_key_helper_spec.rb index ca951ccf485..0c07ecf90ce 100644 --- a/spec/helpers/admin/deploy_key_helper_spec.rb +++ b/spec/helpers/admin/deploy_key_helper_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Admin::DeployKeyHelper do let_it_be(:edit_path) { '/admin/deploy_keys/:id/edit' } let_it_be(:delete_path) { '/admin/deploy_keys/:id' } let_it_be(:create_path) { '/admin/deploy_keys/new' } - let_it_be(:empty_state_svg_path) { '/assets/illustrations/empty-state/empty-deploy-keys-lg.svg' } + let_it_be(:empty_state_svg_path) { '/assets/illustrations/empty-state/empty-access-token-md.svg' } subject(:result) { helper.admin_deploy_keys_data } @@ -15,7 +15,7 @@ RSpec.describe Admin::DeployKeyHelper do expect(helper).to receive(:edit_admin_deploy_key_path).with(':id').and_return(edit_path) expect(helper).to receive(:admin_deploy_key_path).with(':id').and_return(delete_path) expect(helper).to receive(:new_admin_deploy_key_path).and_return(create_path) - expect(helper).to receive(:image_path).with('illustrations/empty-state/empty-deploy-keys-lg.svg').and_return(empty_state_svg_path) + expect(helper).to receive(:image_path).with('illustrations/empty-state/empty-access-token-md.svg').and_return(empty_state_svg_path) expect(result).to eq({ edit_path: edit_path, diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 6ef57f8e22c..ad81c125055 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -845,4 +845,50 @@ RSpec.describe ApplicationHelper do end end end + + describe '#hidden_resource_icon', feature_category: :insider_threat do + let_it_be(:mock_svg) { '<svg></svg>'.html_safe } + + shared_examples 'returns icon with tooltip' do + before do + allow(helper).to receive(:sprite_icon).with('spam', css_class: 'gl-vertical-align-text-bottom').and_return(mock_svg) + end + + it 'returns icon with tooltip' do + result = helper.hidden_resource_icon(resource) + expect(result).to eq("<span class=\"has-tooltip\" title=\"#{expected_title}\">#{mock_svg}</span>") + end + end + + context 'when resource is an issue' do + let_it_be(:resource) { build(:issue) } + let(:expected_title) { 'This issue is hidden because its author has been banned' } + + it_behaves_like 'returns icon with tooltip' + end + + context 'when resource is a merge request' do + let_it_be(:resource) { build(:merge_request) } + let(:expected_title) { 'This merge request is hidden because its author has been banned' } + + it_behaves_like 'returns icon with tooltip' + end + + context 'when resource is a project' do + let_it_be(:resource) { build(:project) } + let(:expected_title) { 'This project is hidden because its creator has been banned' } + + it_behaves_like 'returns icon with tooltip' + end + + context 'when css_class is provided' do + let_it_be(:resource) { build(:issue) } + + it 'passes the value to sprite_icon' do + expect(helper).to receive(:sprite_icon).with('spam', css_class: 'gl-vertical-align-text-bottom extra-class').and_return(mock_svg) + + helper.hidden_resource_icon(resource, css_class: 'extra-class') + end + end + end end diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb index f924704ab54..9d591164547 100644 --- a/spec/helpers/application_settings_helper_spec.rb +++ b/spec/helpers/application_settings_helper_spec.rb @@ -74,6 +74,10 @@ RSpec.describe ApplicationSettingsHelper do expect(helper.visible_attributes).to include(*params) end + it 'contains :namespace_aggregation_schedule_lease_duration_in_seconds' do + expect(helper.visible_attributes).to include(:namespace_aggregation_schedule_lease_duration_in_seconds) + 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) diff --git a/spec/helpers/ci/runners_helper_spec.rb b/spec/helpers/ci/runners_helper_spec.rb index c170d7fae67..febdc3bab65 100644 --- a/spec/helpers/ci/runners_helper_spec.rb +++ b/spec/helpers/ci/runners_helper_spec.rb @@ -83,22 +83,9 @@ RSpec.describe Ci::RunnersHelper, feature_category: :runner_fleet do end 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) } + subject { helper.admin_runners_data_attributes } - before do - allow(helper).to receive(:current_user).and_return(admin) - end - - it 'returns the data in format' do - expect(helper.admin_runners_data_attributes).to include( - 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 - ) - end + it_behaves_like 'admin_runners_data_attributes contains data' end describe '#group_shared_runners_settings_data' do @@ -115,12 +102,19 @@ RSpec.describe Ci::RunnersHelper, feature_category: :runner_fleet do } end + before do + allow(helper).to receive(:can?).with(user, :admin_group, parent).and_return(true) + end + 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_name: nil, + parent_settings_path: nil, parent_shared_runners_setting: nil }.merge(runner_constants) @@ -133,7 +127,27 @@ RSpec.describe Ci::RunnersHelper, feature_category: :runner_fleet do group_name: group.name, group_is_empty: 'true', shared_runners_setting: Namespace::SR_DISABLED_AND_UNOVERRIDABLE, - parent_shared_runners_setting: Namespace::SR_ENABLED + + parent_shared_runners_setting: Namespace::SR_ENABLED, + parent_name: parent.name, + parent_settings_path: group_settings_ci_cd_path(group.parent, anchor: 'runners-settings') + }.merge(runner_constants) + + expect(helper.group_shared_runners_settings_data(group)).to eq result + end + + it 'returns groups data for child group with no access to parent' do + allow(helper).to receive(:can?).with(user, :admin_group, parent).and_return(false) + + 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, + parent_name: nil, + parent_settings_path: nil }.merge(runner_constants) expect(helper.group_shared_runners_settings_data(group)).to eq result @@ -145,7 +159,10 @@ RSpec.describe Ci::RunnersHelper, feature_category: :runner_fleet do group_name: group_with_project.name, group_is_empty: 'false', shared_runners_setting: Namespace::SR_ENABLED, - parent_shared_runners_setting: Namespace::SR_ENABLED + + parent_shared_runners_setting: Namespace::SR_ENABLED, + parent_name: parent.name, + parent_settings_path: group_settings_ci_cd_path(group.parent, anchor: 'runners-settings') }.merge(runner_constants) expect(helper.group_shared_runners_settings_data(group_with_project)).to eq result @@ -190,8 +207,28 @@ RSpec.describe Ci::RunnersHelper, feature_category: :runner_fleet do context 'when project has runners' do it 'returns the correct value for is_enabled' do + allow(helper).to receive(:can?).with(user, :admin_group, group).and_return(false) + + data = helper.toggle_shared_runners_settings_data(project_with_runners) + + expect(data).to include( + is_enabled: 'true', + group_name: nil, + group_settings_path: nil + ) + end + end + + context 'when group can be configured by user' do + it 'returns values to configure group' do + allow(helper).to receive(:can?).with(user, :admin_group, group).and_return(true) + data = helper.toggle_shared_runners_settings_data(project_with_runners) - expect(data[:is_enabled]).to eq("true") + + expect(data).to include( + group_name: group.name, + group_settings_path: group_settings_ci_cd_path(group, anchor: 'runners-settings') + ) end end @@ -218,9 +255,9 @@ RSpec.describe Ci::RunnersHelper, feature_category: :runner_fleet do using RSpec::Parameterized::TableSyntax where(:shared_runners_setting, :is_disabled_and_unoverridable) do - :shared_runners_enabled | "false" - :disabled_and_overridable | "false" - :disabled_and_unoverridable | "true" + :shared_runners_enabled | "false" + :shared_runners_disabled_and_overridable | "false" + :shared_runners_disabled_and_unoverridable | "true" end with_them do diff --git a/spec/helpers/ci/variables_helper_spec.rb b/spec/helpers/ci/variables_helper_spec.rb index 9c3236ace72..13970dd95b4 100644 --- a/spec/helpers/ci/variables_helper_spec.rb +++ b/spec/helpers/ci/variables_helper_spec.rb @@ -3,9 +3,71 @@ require 'spec_helper' RSpec.describe Ci::VariablesHelper, feature_category: :secrets_management do + describe '#create_deploy_token_path' do + let_it_be(:group) { build_stubbed(:group) } + let_it_be(:project) { build_stubbed(:project) } + + it 'returns the project deploy token path' do + expect(helper.create_deploy_token_path(project)).to eq( + create_deploy_token_project_settings_repository_path(project, {}) + ) + end + + it 'returns the group deploy token path' do + expect(helper.create_deploy_token_path(group)).to eq( + create_deploy_token_group_settings_repository_path(group, {}) + ) + end + end + + describe '#ci_variable_protected?' do + let(:variable) { build_stubbed(:ci_variable, key: 'test_key', value: 'test_value', protected: true) } + + context 'when variable is provided and only_key_value is false' do + it 'expect ci_variable_protected? to return true' do + expect(helper.ci_variable_protected?(variable, false)).to eq(true) + end + end + + context 'when variable is not provided / provided and only_key_value is true' do + it 'is equal to the value of ci_variable_protected_by_default?' do + expect(helper.ci_variable_protected?(nil, true)).to eq( + helper.ci_variable_protected_by_default? + ) + + expect(helper.ci_variable_protected?(variable, true)).to eq( + helper.ci_variable_protected_by_default? + ) + end + end + end + + describe '#ci_variable_masked?' do + let(:variable) { build_stubbed(:ci_variable, key: 'test_key', value: 'test_value', masked: true) } + + context 'when variable is provided and only_key_value is false' do + it 'expect ci_variable_masked? to return true' do + expect(helper.ci_variable_masked?(variable, false)).to eq(true) + end + end + + context 'when variable is not provided / provided and only_key_value is true' do + it 'expect ci_variable_masked? to return false' do + expect(helper.ci_variable_masked?(nil, true)).to eq(false) + expect(helper.ci_variable_masked?(variable, true)).to eq(false) + end + end + end + 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,}$") end end + + describe '#ci_variable_maskable_regex' do + it 'converts to a javascript regex' do + expect(helper.ci_variable_maskable_regex).to eq("^[a-zA-Z0-9_+=/@:.~-]{8,}$") + end + end end diff --git a/spec/helpers/commits_helper_spec.rb b/spec/helpers/commits_helper_spec.rb index 2d06f42dee4..49adba22ebe 100644 --- a/spec/helpers/commits_helper_spec.rb +++ b/spec/helpers/commits_helper_spec.rb @@ -91,22 +91,22 @@ RSpec.describe CommitsHelper do let(:node) { Nokogiri::HTML.parse(helper.diff_mode_swap_button(keyword, 'abc')).at_css('a') } context 'for rendered' do - it 'renders the correct select-rendered button' do + it 'renders the correct select-rendered button', :aggregate_failures do expect(node[:title]).to eq('Display rendered diff') expect(node['data-file-hash']).to eq('abc') expect(node['data-diff-toggle-entity']).to eq('renderedButton') - expect(node.xpath("//a/svg")[0]["data-testid"]).to eq('doc-text-icon') + expect(node.xpath("//a/span/svg")[0]["data-testid"]).to eq('doc-text-icon') end end context 'for raw' do let(:keyword) { 'raw' } - it 'renders the correct select-raw button' do + it 'renders the correct select-raw button', :aggregate_failures do expect(node[:title]).to eq('Display raw diff') expect(node['data-file-hash']).to eq('abc') expect(node['data-diff-toggle-entity']).to eq('rawButton') - expect(node.xpath("//a/svg")[0]["data-testid"]).to eq('doc-code-icon') + expect(node.xpath("//a/span/svg")[0]["data-testid"]).to eq('doc-code-icon') end end end @@ -357,4 +357,46 @@ RSpec.describe CommitsHelper do it { is_expected.to eq(expected_path) } end + + describe '#local_committed_date' do + let(:commit) { build(:commit, committed_date: time) } + let(:user) { build(:user) } + let(:time) { Time.find_zone('UTC').parse('2023-01-01') } + + subject { helper.local_committed_date(commit, user).to_s } + + it { is_expected.to eq('2023-01-01') } + + context 'when user has a custom timezone' do + let(:user) { build(:user, timezone: 'America/Mexico_City') } + + it 'selects timezone of the user' do + is_expected.to eq('2022-12-31') + end + end + + context "when user doesn't have a preferred timezone" do + let(:user) { build(:user, timezone: nil) } + + it 'uses system timezone' do + is_expected.to eq('2023-01-01') + end + end + + context 'when user timezone is not supported' do + let(:user) { build(:user, timezone: 'unknown') } + + it 'uses system timezone' do + is_expected.to eq('2023-01-01') + end + end + + context 'when user is missing' do + let(:user) { nil } + + it 'uses system timezone' do + is_expected.to eq('2023-01-01') + end + end + end end diff --git a/spec/helpers/environments_helper_spec.rb b/spec/helpers/environments_helper_spec.rb index b69d6022e70..c0c729f2b67 100644 --- a/spec/helpers/environments_helper_spec.rb +++ b/spec/helpers/environments_helper_spec.rb @@ -41,8 +41,7 @@ RSpec.describe EnvironmentsHelper, feature_category: :environment_management do 'custom_metrics_available' => 'true', 'custom_dashboard_base_path' => Gitlab::Metrics::Dashboard::RepoDashboardFinder::DASHBOARD_ROOT, 'operations_settings_path' => project_settings_operations_path(project), - 'can_access_operations_settings' => 'true', - 'panel_preview_endpoint' => project_metrics_dashboards_builder_path(project, format: :json) + 'can_access_operations_settings' => 'true' ) end diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb index 39901047b0f..6ffca876361 100644 --- a/spec/helpers/events_helper_spec.rb +++ b/spec/helpers/events_helper_spec.rb @@ -2,10 +2,20 @@ require 'spec_helper' -RSpec.describe EventsHelper do +# Persisting records is required because Event#target's AR scope. +# We are trying hard to minimize record creations by: +# * Using `let_it_be` +# * Factory defaults via `create_default` + `factory_default: :keep` +# +# rubocop:disable RSpec/FactoryBot/AvoidCreate +RSpec.describe EventsHelper, factory_default: :keep, feature_category: :user_profile do include Gitlab::Routing include Banzai::Filter::OutputSafety + let_it_be(:project) { create_default(:project).freeze } + let_it_be(:project_with_repo) { create(:project, :public, :repository).freeze } + let_it_be(:user) { create_default(:user).freeze } + describe '#link_to_author' do let(:user) { create(:user) } let(:event) { create(:event, author: user) } @@ -40,9 +50,8 @@ RSpec.describe EventsHelper do end context 'when target is not a work item' do - let(:project) { create(:project) } - let(:issue) { create(:issue, project: project) } - let(:event) { create(:event, target: issue, project: project) } + let(:issue) { create(:issue) } + let(:event) { create(:event, target: issue) } it { is_expected.to eq([project, issue]) } end @@ -51,7 +60,7 @@ RSpec.describe EventsHelper do describe '#localized_action_name' do it 'handles all valid design events' do created, updated, destroyed = %i[created updated destroyed].map do |trait| - event = build(:design_event, trait) + event = build_stubbed(:design_event, trait) helper.localized_action_name(event) end @@ -60,44 +69,46 @@ RSpec.describe EventsHelper do expect(destroyed).to eq(_('removed')) end - context 'handles correct base actions' do + describe 'handles correct base actions' do using RSpec::Parameterized::TableSyntax - where(:trait, :localized_action_name) do - :created | s_('Event|created') - :updated | s_('Event|opened') - :closed | s_('Event|closed') - :reopened | s_('Event|opened') - :commented | s_('Event|commented on') - :merged | s_('Event|accepted') - :joined | s_('Event|joined') - :left | s_('Event|left') - :destroyed | s_('Event|destroyed') - :expired | s_('Event|removed due to membership expiration from') - :approved | s_('Event|approved') + where(:trait, :localized_action_key) do + :created | 'Event|created' + :updated | 'Event|opened' + :closed | 'Event|closed' + :reopened | 'Event|opened' + :commented | 'Event|commented on' + :merged | 'Event|accepted' + :joined | 'Event|joined' + :left | 'Event|left' + :destroyed | 'Event|destroyed' + :expired | 'Event|removed due to membership expiration from' + :approved | 'Event|approved' end with_them do it 'with correct name and method' do - event = build(:event, trait) + Gitlab::I18n.with_locale(:de) do + event = build_stubbed(:event, trait) - expect(helper.localized_action_name(event)).to eq(localized_action_name) + expect(helper.localized_action_name(event)).to eq(s_(localized_action_key)) + end end end end end describe '#event_commit_title' do - let(:message) { 'foo & bar ' + 'A' * 70 + '\n' + 'B' * 80 } + let(:message) { "foo & bar #{'A' * 70}\\n#{'B' * 80}" } subject { helper.event_commit_title(message) } it 'returns the first line, truncated to 70 chars' do - is_expected.to eq(message[0..66] + "...") + is_expected.to eq("#{message[0..66]}...") end it 'is not html-safe' do - is_expected.not_to be_a(ActiveSupport::SafeBuffer) + is_expected.not_to be_html_safe end it 'handles empty strings' do @@ -115,9 +126,8 @@ RSpec.describe EventsHelper do describe '#event_feed_url' do let(:event) { create(:event).present } - let(:project) { create(:project, :public, :repository) } - context 'issue' do + context 'for issue' do before do event.target = create(:issue) end @@ -131,9 +141,9 @@ RSpec.describe EventsHelper do end end - context 'merge request' do + context 'for merge request' do before do - event.target = create(:merge_request) + event.target = create(:merge_request, source_project: project_with_repo) end it 'returns the project merge request url' do @@ -146,7 +156,7 @@ RSpec.describe EventsHelper do end it 'returns project commit url' do - event.target = create(:note_on_commit, project: project) + event.target = create(:note_on_commit, project: project_with_repo) expect(helper.event_feed_url(event)).to eq(project_commit_url(event.project, event.note_target)) end @@ -158,7 +168,6 @@ RSpec.describe EventsHelper do end it 'returns project url' do - event.project = project event.action = 1 expect(helper.event_feed_url(event)).to eq(project_url(event.project)) @@ -173,7 +182,8 @@ RSpec.describe EventsHelper do it 'returns nil for push event with multiple refs' do event = create(:push_event) - create(:push_event_payload, event: event, ref_count: 2, ref: nil, ref_type: :tag, commit_count: 0, action: :pushed) + create(:push_event_payload, event: event, ref_count: 2, ref: nil, ref_type: :tag, commit_count: 0, + action: :pushed) expect(helper.event_feed_url(event)).to eq(nil) end @@ -229,8 +239,8 @@ RSpec.describe EventsHelper do end end - describe 'event_wiki_page_target_url' do - let(:project) { create(:project) } + describe '#event_wiki_page_target_url' do + let_it_be_with_reload(:project) { create(:project) } let(:wiki_page) { create(:wiki_page, wiki: create(:project_wiki, project: project)) } let(:event) { create(:wiki_page_event, project: project, wiki_page: wiki_page) } @@ -240,7 +250,7 @@ RSpec.describe EventsHelper do expect(helper.event_wiki_page_target_url(event)).to eq(url) end - context 'there is no canonical slug' do + context 'without canonical slug' do let(:event) { create(:wiki_page_event, project: project) } before do @@ -274,14 +284,13 @@ RSpec.describe EventsHelper do end describe '#event_note_target_url' do - let(:project) { create(:project, :public, :repository) } - let(:event) { create(:event, project: project) } + let_it_be(:event) { create(:event) } let(:project_base_url) { namespace_project_url(namespace_id: project.namespace, id: project) } subject { helper.event_note_target_url(event) } it 'returns a commit note url' do - event.target = create(:note_on_commit, note: '+1 from me') + event.target = create(:note_on_commit, project: project_with_repo, note: '+1 from me') expect(subject).to eq("#{project_base_url}/-/commit/#{event.target.commit_id}#note_#{event.target.id}") end @@ -289,7 +298,8 @@ RSpec.describe EventsHelper do it 'returns a project snippet note url' do event.target = create(:note_on_project_snippet, note: 'keep going') - expect(subject).to eq("#{project_snippet_url(event.note_target.project, event.note_target)}#note_#{event.target.id}") + expect(subject).to eq("#{project_snippet_url(event.note_target.project, + event.note_target)}#note_#{event.target.id}") end it 'returns a personal snippet note url' do @@ -311,7 +321,7 @@ RSpec.describe EventsHelper do end context 'for design note events' do - let(:event) { create(:event, :for_design, project: project) } + let(:event) { create(:event, :for_design) } it 'returns an appropriate URL' do iid = event.note_target.issue.iid @@ -326,54 +336,62 @@ RSpec.describe EventsHelper do describe '#event_filter_visible' do include DesignManagementTestHelpers - let_it_be(:project) { create(:project) } - let_it_be(:current_user) { create(:user) } - subject { helper.event_filter_visible(key) } before do enable_design_management - project.add_reporter(current_user) - allow(helper).to receive(:current_user).and_return(current_user) + allow(helper).to receive(:current_user).and_return(user) end - def disable_read_design_activity(object) + def can_read_design_activity(object, ability) allow(Ability).to receive(:allowed?) - .with(current_user, :read_design_activity, eq(object)) - .and_return(false) + .with(user, :read_design_activity, eq(object)) + .and_return(ability) end context 'for :designs' do let(:key) { :designs } - context 'there is no relevant instance variable' do + context 'without relevant instance variable' do it { is_expected.to be(true) } end - context 'a project has been assigned' do + context 'with assigned project' do before do assign(:project, project) end - it { is_expected.to be(true) } + context 'with permission' do + before do + can_read_design_activity(project, true) + end + + it { is_expected.to be(true) } + end - context 'the current user cannot read design activity' do + context 'without permission' do before do - disable_read_design_activity(project) + can_read_design_activity(project, false) end it { is_expected.to be(false) } end end - context 'projects have been assigned' do + context 'with projects assigned' do before do - assign(:projects, Project.where(id: project.id)) + assign(:projects, Project.id_in(project)) end - it { is_expected.to be(true) } + context 'with permission' do + before do + can_read_design_activity(project, true) + end + + it { is_expected.to be(true) } + end - context 'the collection is empty' do + context 'with empty collection' do before do assign(:projects, Project.none) end @@ -381,36 +399,40 @@ RSpec.describe EventsHelper do it { is_expected.to be(false) } end - context 'the current user cannot read design activity' do + context 'without permission' do before do - disable_read_design_activity(project) + can_read_design_activity(project, false) end it { is_expected.to be(false) } end end - context 'a group has been assigned' do + context 'with group assigned' do let_it_be(:group) { create(:group) } before do assign(:group, group) end - context 'there are no projects in the group' do + context 'without projects in the group' do it { is_expected.to be(false) } end - context 'the group has at least one project' do - before do - create(:project_group_link, project: project, group: group) - end + context 'with at least one project in the project' do + let_it_be(:group_link) { create(:project_group_link, group: group) } - it { is_expected.to be(true) } + context 'with permission' do + before do + can_read_design_activity(group, true) + end + + it { is_expected.to be(true) } + end - context 'the current user cannot read design activity' do + context 'without permission' do before do - disable_read_design_activity(group) + can_read_design_activity(group, false) end it { is_expected.to be(false) } @@ -420,3 +442,4 @@ RSpec.describe EventsHelper do end end end +# rubocop:enable RSpec/FactoryBot/AvoidCreate diff --git a/spec/helpers/integrations_helper_spec.rb b/spec/helpers/integrations_helper_spec.rb index f481611b2a2..6c5a489e664 100644 --- a/spec/helpers/integrations_helper_spec.rb +++ b/spec/helpers/integrations_helper_spec.rb @@ -271,6 +271,7 @@ RSpec.describe IntegrationsHelper, feature_category: :integrations do "test_case" | _('Test case') "requirement" | _('Requirement') "task" | _('Task') + "ticket" | _('Service Desk Ticket') end with_them do @@ -285,7 +286,7 @@ RSpec.describe IntegrationsHelper, feature_category: :integrations do end it "only consider these enumeration values are valid" do - expected_valid_types = %w[issue incident test_case requirement task objective key_result] + expected_valid_types = %w[issue incident test_case requirement task objective key_result epic ticket] 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 a2b8ee061bb..7b5537c54cc 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -524,6 +524,64 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do end end end + + describe '#duplicatedToIssueUrl' do + let(:issue) { create(:issue, author: user) } + + before do + assign(:project, issue.project) + end + + context 'when issue is duplicated' do + before do + allow(issue).to receive(:duplicated?).and_return(true) + allow(issue).to receive(:duplicated_to).and_return(issue) + end + + it 'returns url' do + expect(helper.issuable_initial_data(issue)[:duplicatedToIssueUrl]).to be_truthy + end + end + + context 'when issue is not duplicated' do + before do + allow(issue).to receive(:duplicated?).and_return(false) + end + + it 'returns nil' do + expect(helper.issuable_initial_data(issue)[:duplicatedToIssueUrl]).to be_nil + end + end + end + + describe '#movedToIssueUrl' do + let(:issue) { create(:issue, author: user) } + + before do + assign(:project, issue.project) + end + + context 'when issue is moved' do + before do + allow(issue).to receive(:moved?).and_return(true) + allow(issue).to receive(:moved_to).and_return(issue) + end + + it 'returns url' do + expect(helper.issuable_initial_data(issue)[:movedToIssueUrl]).to be_truthy + end + end + + context 'when issue is not moved' do + before do + allow(issue).to receive(:moved?).and_return(false) + end + + it 'returns nil' do + expect(helper.issuable_initial_data(issue)[:movedToIssueUrl]).to be_nil + end + end + end end describe '#assignee_sidebar_data' do @@ -674,30 +732,6 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do end end - describe '#hidden_issuable_icon', feature_category: :insider_threat do - let_it_be(:mock_svg) { '<svg></svg>'.html_safe } - - before do - allow(helper).to receive(:sprite_icon).and_return(mock_svg) - end - - context 'when issuable is an issue' do - let_it_be(:issuable) { build(:issue) } - - it 'returns icon with tooltip' do - expect(helper.hidden_issuable_icon(issuable)).to eq("<span class=\"has-tooltip\" title=\"This issue is hidden because its author has been banned\">#{mock_svg}</span>") - end - end - - context 'when issuable is a merge request' do - let_it_be(:issuable) { build(:merge_request) } - - it 'returns icon with tooltip' do - expect(helper.hidden_issuable_icon(issuable)).to eq("<span class=\"has-tooltip\" title=\"This merge request is hidden because its author has been banned\">#{mock_svg}</span>") - end - end - end - describe '#issuable_type_selector_data' do using RSpec::Parameterized::TableSyntax diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index ba323140720..0cde9aeac8d 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -486,23 +486,26 @@ RSpec.describe IssuesHelper do end describe '#hidden_issue_icon' do - let_it_be(:banned_user) { build(:user, :banned) } - let_it_be(:hidden_issue) { build(:issue, author: banned_user) } let_it_be(:mock_svg) { '<svg></svg>'.html_safe } before do - allow(helper).to receive(:sprite_icon).and_return(mock_svg) + allow(helper).to receive(:hidden_resource_icon).with(resource).and_return(mock_svg) end context 'when issue is hidden' do + let_it_be(:banned_user) { build(:user, :banned) } + let_it_be(:resource) { build(:issue, author: banned_user) } + it 'returns icon with tooltip' do - expect(helper.hidden_issue_icon(hidden_issue)).to eq("<span class=\"has-tooltip\" title=\"This issue is hidden because its author has been banned\">#{mock_svg}</span>") + expect(helper.hidden_issue_icon(resource)).to eq(mock_svg) end end context 'when issue is not hidden' do + let_it_be(:resource) { issue } + it 'returns `nil`' do - expect(helper.hidden_issue_icon(issue)).to be_nil + expect(helper.hidden_issue_icon(resource)).to be_nil end end end diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index b4549630813..4877ab1ff03 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -275,9 +275,18 @@ RSpec.describe LabelsHelper do let(:html) { '<img src="example.png">This is an image</img>' } let(:label_with_html_content) { create(:label, title: 'test', description: html) } - it 'removes HTML' do - tooltip = label_tooltip_title(label_with_html_content) - expect(tooltip).to eq('This is an image') + context 'tooltip shows description' do + it 'removes HTML' do + tooltip = label_tooltip_title(label_with_html_content) + expect(tooltip).to eq('This is an image') + end + end + + context 'tooltip shows title' do + it 'shows title' do + tooltip = label_tooltip_title(label_with_html_content, tooltip_shows_title: true) + expect(tooltip).to eq('test') + end end end diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index 562d6683d97..22d1113ee8c 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MarkupHelper do +RSpec.describe MarkupHelper, feature_category: :team_planning do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) do user = create(:user, username: 'gfm') @@ -461,7 +461,7 @@ RSpec.describe MarkupHelper do it 'displays the first line of a code block' do object = create_object("```\nCode block\nwith two lines\n```") - expected = %r{<pre.+><code><span class="line">Code block\.\.\.</span>\n</code></pre>} + expected = %r{<pre.+><code><span class="line">Code block\.\.\.</span></code></pre>} expect(helper.first_line_in_markdown(object, attribute, 100, is_todo: true, project: project)).to match(expected) end @@ -477,7 +477,7 @@ RSpec.describe MarkupHelper do it 'preserves code color scheme' do object = create_object("```ruby\ndef test\n 'hello world'\nend\n```") expected = "\n<pre class=\"code highlight js-syntax-highlight language-ruby\">" \ - "<code><span class=\"line\"><span class=\"k\">def</span> <span class=\"nf\">test</span>...</span>\n" \ + "<code><span class=\"line\"><span class=\"k\">def</span> <span class=\"nf\">test</span>...</span>" \ "</code></pre>\n" expect(helper.first_line_in_markdown(object, attribute, 150, is_todo: true, project: project)).to eq(expected) diff --git a/spec/helpers/nav_helper_spec.rb b/spec/helpers/nav_helper_spec.rb index 4a02b184522..4b83561b265 100644 --- a/spec/helpers/nav_helper_spec.rb +++ b/spec/helpers/nav_helper_spec.rb @@ -169,15 +169,39 @@ RSpec.describe NavHelper, feature_category: :navigation do end end - context 'when nil is provided' do - specify { expect(helper.show_super_sidebar?(nil)).to eq false } + shared_examples 'anonymous show_super_sidebar is supposed to' do + before do + stub_feature_flags(super_sidebar_logged_out: feature_flag) + end + + context 'when super_sidebar_logged_out feature flag is disabled' do + let(:feature_flag) { false } + + specify { expect(subject).to eq false } + end + + context 'when super_sidebar_logged_out feature flag is enabled' do + let(:feature_flag) { true } + + specify { expect(subject).to eq true } + end end - context 'when no user is signed-in' do - specify do - allow(helper).to receive(:current_user).and_return(nil) + context 'without a user' do + context 'with current_user (nil) as a default' do + before do + allow(helper).to receive(:current_user).and_return(nil) + end + + subject { helper.show_super_sidebar? } + + it_behaves_like 'anonymous show_super_sidebar is supposed to' + end + + context 'with nil provided as an argument' do + subject { helper.show_super_sidebar?(nil) } - expect(helper.show_super_sidebar?).to eq false + it_behaves_like 'anonymous show_super_sidebar is supposed to' end end diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb index 91635ffcdc0..62c0d1b1ff7 100644 --- a/spec/helpers/notes_helper_spec.rb +++ b/spec/helpers/notes_helper_spec.rb @@ -331,7 +331,9 @@ RSpec.describe NotesHelper, feature_category: :team_planning do end describe '#notes_data' do - let(:issue) { create(:issue, project: project) } + let_it_be(:issue) { create(:issue, project: project) } + + let(:notes_data) { helper.notes_data(issue) } before do @project = project @@ -343,7 +345,14 @@ RSpec.describe NotesHelper, feature_category: :team_planning do it 'includes the current notes filter for the user' do guest.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issue) - expect(helper.notes_data(issue)[:notesFilter]).to eq(UserPreference::NOTES_FILTERS[:only_comments]) + expect(notes_data[:notesFilter]).to eq(UserPreference::NOTES_FILTERS[:only_comments]) + end + + it 'includes info about the noteable', :aggregate_failures do + expect(notes_data[:noteableType]).to eq('issue') + expect(notes_data[:noteableId]).to eq(issue.id) + expect(notes_data[:projectId]).to eq(project.id) + expect(notes_data[:groupId]).to be_nil end end end diff --git a/spec/helpers/profiles_helper_spec.rb b/spec/helpers/profiles_helper_spec.rb index 4c43b1ec4cf..15ca5f61b51 100644 --- a/spec/helpers/profiles_helper_spec.rb +++ b/spec/helpers/profiles_helper_spec.rb @@ -124,6 +124,41 @@ RSpec.describe ProfilesHelper do end end + describe '#user_profile_data' do + let(:time) { 3.hours.ago } + let(:user) do + build_stubbed(:user, status: UserStatus.new( + message: 'Some message', + emoji: 'basketball', + availability: 'busy', + clear_status_at: time + )) + end + + before do + allow(helper).to receive(:current_user).and_return(user) + end + + it 'returns user profile data' do + data = helper.user_profile_data(user) + + expect(data[:profile_path]).to be_a(String) + expect(data[:profile_avatar_path]).to be_a(String) + expect(data[:avatar_url]).to be_http_url + expect(data[:has_avatar]).to be_a(String) + expect(data[:gravatar_enabled]).to be_a(String) + expect(Gitlab::Json.parse(data[:gravatar_link])).to match(hash_including('hostname' => Gitlab.config.gravatar.host, 'url' => a_valid_url)) + expect(data[:brand_profile_image_guidelines]).to be_a(String) + expect(data[:cropper_css_path]).to eq(ActionController::Base.helpers.stylesheet_path('lazy_bundles/cropper.css')) + expect(data[:user_path]).to be_a(String) + expect(data[:current_emoji]).to eq('basketball') + expect(data[:current_message]).to eq('Some message') + expect(data[:current_availability]).to eq('busy') + expect(data[:current_clear_status_after]).to eq(time.to_fs(:iso8601)) + expect(data[:default_emoji]).to eq(UserStatus::DEFAULT_EMOJI) + end + end + def stub_auth0_omniauth_provider provider = OpenStruct.new( 'name' => example_omniauth_provider, diff --git a/spec/helpers/projects/observability_helper_spec.rb b/spec/helpers/projects/observability_helper_spec.rb index 65b6ddf04ec..0f47cdb8be2 100644 --- a/spec/helpers/projects/observability_helper_spec.rb +++ b/spec/helpers/projects/observability_helper_spec.rb @@ -4,10 +4,12 @@ require 'spec_helper' require 'json' RSpec.describe Projects::ObservabilityHelper, type: :helper, feature_category: :tracing do - describe '#observability_tracing_view_model' do - let_it_be(:group) { build_stubbed(:group) } - let_it_be(:project) { build_stubbed(:project, group: group) } + include Gitlab::Routing.url_helpers + + let_it_be(:group) { build_stubbed(:group) } + let_it_be(:project) { build_stubbed(:project, group: group) } + describe '#observability_tracing_view_model' do it 'generates the correct JSON' do expected_json = { tracingUrl: Gitlab::Observability.tracing_url(project), @@ -18,4 +20,18 @@ RSpec.describe Projects::ObservabilityHelper, type: :helper, feature_category: : expect(helper.observability_tracing_view_model(project)).to eq(expected_json) end end + + describe '#observability_tracing_details_model' do + it 'generates the correct JSON' do + expected_json = { + tracingIndexUrl: namespace_project_tracing_index_path(project.group, project), + traceId: "trace-id", + tracingUrl: Gitlab::Observability.tracing_url(project), + provisioningUrl: Gitlab::Observability.provisioning_url(project), + oauthUrl: Gitlab::Observability.oauth_url + }.to_json + + expect(helper.observability_tracing_details_model(project, "trace-id")).to eq(expected_json) + end + end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 768038d8736..aa064a26ec4 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -993,6 +993,7 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do expect(settings).to include( packagesEnabled: !!project.packages_enabled, + packageRegistryAllowAnyoneToPullOption: ::Gitlab::CurrentSettings.package_registry_allow_anyone_to_pull_option, visibilityLevel: project.visibility_level, requestAccessEnabled: !!project.request_access_enabled, issuesAccessLevel: project.project_feature.issues_access_level, @@ -1006,7 +1007,7 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do analyticsAccessLevel: project.project_feature.analytics_access_level, containerRegistryEnabled: !!project.container_registry_enabled, lfsEnabled: !!project.lfs_enabled, - emailsDisabled: project.emails_disabled?, + emailsEnabled: project.emails_enabled?, showDefaultAwardEmojis: project.show_default_award_emojis?, securityAndComplianceAccessLevel: project.security_and_compliance_access_level, containerRegistryAccessLevel: project.project_feature.container_registry_access_level, @@ -1129,16 +1130,39 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do end end - describe '#fork_button_disabled_tooltip' do + describe '#fork_button_data_attributes' do using RSpec::Parameterized::TableSyntax - subject { helper.fork_button_disabled_tooltip(project) } + let_it_be(:project) { create(:project, :repository, :public) } - where(:has_user, :can_fork_project, :can_create_fork, :expected) do - false | false | false | nil - true | true | true | nil - true | false | true | 'You don\'t have permission to fork this project' - true | true | false | 'You have reached your project limit' + project_path = '/project/path' + project_forks_path = '/project/forks' + project_new_fork_path = '/project/new/fork' + user_fork_url = '/user/fork' + + common_data_attributes = { + forks_count: 4, + project_full_path: project_path, + project_forks_url: project_forks_path, + can_create_fork: "true", + can_fork_project: "true", + can_read_code: "true", + new_fork_url: project_new_fork_path + } + + data_attributes_with_user_fork_url = common_data_attributes.merge({ user_fork_url: user_fork_url }) + data_attributes_without_user_fork_url = common_data_attributes.merge({ user_fork_url: nil }) + + subject { helper.fork_button_data_attributes(project) } + + # The stubs for the forkable namespaces seem not to make sense (they're just numbers), + # but they're set up that way because we don't really care about what the array contains, only about its length + where(:has_user, :project_already_forked, :forkable_namespaces, :expected) do + false | false | [] | nil + true | false | [0] | data_attributes_without_user_fork_url + true | false | [0, 1] | data_attributes_without_user_fork_url + true | true | [0] | data_attributes_with_user_fork_url + true | true | [0, 1] | data_attributes_without_user_fork_url end with_them do @@ -1146,13 +1170,22 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do current_user = user if has_user allow(helper).to receive(:current_user).and_return(current_user) - allow(user).to receive(:can?).with(:fork_project, project).and_return(can_fork_project) - allow(user).to receive(:can?).with(:create_fork).and_return(can_create_fork) - end + allow(user).to receive(:can?).with(:fork_project, project).and_return(true) + allow(user).to receive(:can?).with(:create_fork).and_return(true) + allow(user).to receive(:can?).with(:create_projects, anything).and_return(true) + allow(user).to receive(:already_forked?).with(project).and_return(project_already_forked) + allow(user).to receive(:forkable_namespaces).and_return(forkable_namespaces) - it 'returns tooltip text when user lacks privilege' do - expect(subject).to eq(expected) + allow(project).to receive(:forks_count).and_return(4) + allow(project).to receive(:full_path).and_return(project_path) + + user_fork_path = user_fork_url if project_already_forked + allow(helper).to receive(:namespace_project_path).with(user, anything).and_return(user_fork_path) + allow(helper).to receive(:new_project_fork_path).with(project).and_return(project_new_fork_path) + allow(helper).to receive(:project_forks_path).with(project).and_return(project_forks_path) end + + it { is_expected.to eq(expected) } end end @@ -1614,4 +1647,62 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do it { is_expected.to eq(project_settings_repository_path(project, anchor: 'js-branch-rules')) } end + + describe '#visibility_level_content' do + shared_examples 'returns visibility level content_tag' do + let(:icon) { '<svg>fake visib level icon</svg>'.html_safe } + let(:description) { 'Fake visib desc' } + + before do + allow(helper).to receive(:visibility_icon_description).and_return(description) + allow(helper).to receive(:visibility_level_icon).and_return(icon) + end + + it 'returns visibility level content_tag' do + expected_result = "<span class=\"has-tooltip\" data-container=\"body\" data-placement=\"top\" title=\"#{description}\">#{icon}</span>" + expect(helper.visibility_level_content(project)).to eq(expected_result) + end + + it 'returns visibility level content_tag with extra CSS classes' do + expected_result = "<span class=\"has-tooltip extra-class\" data-container=\"body\" data-placement=\"top\" title=\"#{description}\">#{icon}</span>" + + expect(helper).to receive(:visibility_level_icon) + .with(anything, options: { class: 'extra-icon-class' }) + .and_return(icon) + result = helper.visibility_level_content(project, css_class: 'extra-class', icon_css_class: 'extra-icon-class') + expect(result).to eq(expected_result) + end + end + + it_behaves_like 'returns visibility level content_tag' + + context 'when project creator is banned' do + let(:hidden_resource_icon) { '<svg>fake hidden resource icon</svg>' } + + before do + allow(project).to receive(:created_and_owned_by_banned_user?).and_return(true) + allow(helper).to receive(:hidden_resource_icon).and_return(hidden_resource_icon) + end + + it 'returns hidden resource icon' do + expect(helper.visibility_level_content(project)).to eq hidden_resource_icon + end + end + + context 'with hide_projects_of_banned_users feature flag disabled' do + before do + stub_feature_flags(hide_projects_of_banned_users: false) + end + + it_behaves_like 'returns visibility level content_tag' + + context 'when project creator is banned' do + before do + allow(project).to receive(:created_and_owned_by_banned_user?).and_return(true) + end + + it_behaves_like 'returns visibility level content_tag' + end + end + end end diff --git a/spec/helpers/sessions_helper_spec.rb b/spec/helpers/sessions_helper_spec.rb index 5a46a20ce1a..366032100de 100644 --- a/spec/helpers/sessions_helper_spec.rb +++ b/spec/helpers/sessions_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe SessionsHelper do +RSpec.describe SessionsHelper, feature_category: :system_access do describe '#recently_confirmed_com?' do subject { helper.recently_confirmed_com? } @@ -51,28 +51,66 @@ RSpec.describe SessionsHelper do end end - describe '#send_rate_limited?' do + describe '#unconfirmed_verification_email?', :freeze_time do + using RSpec::Parameterized::TableSyntax + let(:user) { build_stubbed(:user) } + let(:token_valid_for) { ::Users::EmailVerification::ValidateTokenService::TOKEN_VALID_FOR_MINUTES } + + subject { helper.unconfirmed_verification_email?(user) } + + where(:reset_first_offer?, :unconfirmed_email_present?, :token_valid?, :result) do + true | true | true | true + false | true | true | false + true | false | true | false + true | true | false | false + end + + with_them do + before do + user.email_reset_offered_at = 1.minute.ago unless reset_first_offer? + user.unconfirmed_email = 'unconfirmed@email' if unconfirmed_email_present? + user.confirmation_sent_at = (token_valid? ? token_valid_for - 1 : token_valid_for + 1).minutes.ago + end + + it { is_expected.to eq(result) } + end + end + + describe '#verification_email' do + let(:unconfirmed_email) { 'unconfirmed@email' } + let(:user) { build_stubbed(:user, unconfirmed_email: unconfirmed_email) } + + subject { helper.verification_email(user) } - subject { helper.send_rate_limited?(user) } + context 'when there is an unconfirmed verification email' do + before do + allow(helper).to receive(:unconfirmed_verification_email?).and_return(true) + end - before do - allow(::Gitlab::ApplicationRateLimiter) - .to receive(:peek) - .with(:email_verification_code_send, scope: user) - .and_return(rate_limited) + it { is_expected.to eq(unconfirmed_email) } end - context 'when rate limited' do - let(:rate_limited) { true } + context 'when there is no unconfirmed verification email' do + before do + allow(helper).to receive(:unconfirmed_verification_email?).and_return(false) + end - it { is_expected.to eq(true) } + it { is_expected.to eq(user.email) } end + end - context 'when not rate limited' do - let(:rate_limited) { false } + describe '#verification_data' do + let(:user) { build_stubbed(:user) } - it { is_expected.to eq(false) } + it 'returns the expected data' do + expect(helper.verification_data(user)).to eq({ + obfuscated_email: obfuscated_email(user.email), + verify_path: helper.session_path(:user), + resend_path: users_resend_verification_code_path, + offer_email_reset: user.email_reset_offered_at.nil?.to_s, + update_email_path: users_update_email_path + }) end end diff --git a/spec/helpers/sidebars_helper_spec.rb b/spec/helpers/sidebars_helper_spec.rb index 8d8bbcd2737..4109eb01caa 100644 --- a/spec/helpers/sidebars_helper_spec.rb +++ b/spec/helpers/sidebars_helper_spec.rb @@ -91,15 +91,21 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do allow(user).to receive(:pinned_nav_items).and_return({ panel_type => %w[foo bar], 'another_panel' => %w[baz] }) end + # Tests for logged-out sidebar context + it_behaves_like 'logged-out super-sidebar context' + + # Tests for logged-in sidebar context below + it_behaves_like 'shared super sidebar context' + it { is_expected.to include({ is_logged_in: true }) } + 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, + is_logged_in: true, name: user.name, username: user.username, avatar_url: user.avatar_url, has_link_to_profile: helper.current_user_menu?(:profile), - link_to_profile: user_url(user), + link_to_profile: user_path(user), status: { can_update: helper.can?(user, :update_user_status, user), busy: user.status&.busy?, @@ -128,26 +134,11 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do 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_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, + update_pins_url: pins_path, shortcut_links: [ { title: _('Milestones'), @@ -383,11 +374,17 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do 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(:public_links_for_user) do + [ + { title: s_('Navigation|Your work'), link: '/', icon: 'work' }, + *public_link + ] + end + let_it_be(:admin_area_link) do { title: s_('Navigation|Admin Area'), link: '/admin', icon: 'admin' } end @@ -405,12 +402,20 @@ RSpec.describe SidebarsHelper, feature_category: :navigation 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 + context 'when user is not logged in' do + let(:user) { nil } + + it 'returns only the public links for an anonymous user' do expect(subject[:context_switcher_links]).to eq(public_link) end end + context 'when user is not an admin' do + it 'returns only the public links for a user' do + expect(subject[:context_switcher_links]).to eq(public_links_for_user) + end + end + context 'when user is an admin' do before do allow(user).to receive(:admin?).and_return(true) @@ -429,7 +434,7 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do it 'returns public links, admin area and leave admin mode links' do expect(subject[:context_switcher_links]).to eq([ - *public_link, + *public_links_for_user, admin_area_link, leave_admin_mode_link ]) @@ -439,7 +444,7 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do 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, + *public_links_for_user, enter_admin_mode_link ]) end @@ -453,7 +458,7 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do it 'returns public links and admin area link' do expect(subject[:context_switcher_links]).to eq([ - *public_link, + *public_links_for_user, admin_area_link ]) end @@ -471,8 +476,11 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do end describe 'when impersonating' do + before do + session[:impersonator_id] = 5 + end + 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 diff --git a/spec/helpers/snippets_helper_spec.rb b/spec/helpers/snippets_helper_spec.rb index 43e663464c8..1e899396de4 100644 --- a/spec/helpers/snippets_helper_spec.rb +++ b/spec/helpers/snippets_helper_spec.rb @@ -33,7 +33,7 @@ RSpec.describe SnippetsHelper do end def download_link(url) - "<a class=\"gl-button btn btn-default\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Open raw\" href=\"#{url}\">#{external_snippet_icon('doc-code')}</a>" + "<a rel=\"noopener noreferrer\" title=\"Open raw\" class=\"gl-button btn btn-md btn-default \" target=\"_blank\" href=\"#{url}\"><span class=\"gl-button-text\">\n#{external_snippet_icon('doc-code')}\n</span>\n\n</a>" end end @@ -60,7 +60,7 @@ RSpec.describe SnippetsHelper do end def download_link(url) - "<a class=\"gl-button btn btn-default\" target=\"_blank\" title=\"Download\" rel=\"noopener noreferrer\" href=\"#{url}?inline=false\">#{external_snippet_icon('download')}</a>" + "<a rel=\"noopener noreferrer\" title=\"Download\" class=\"gl-button btn btn-md btn-default \" target=\"_blank\" href=\"#{url}?inline=false\"><span class=\"gl-button-text\">\n#{external_snippet_icon('download')}\n</span>\n\n</a>" end end @@ -102,7 +102,7 @@ RSpec.describe SnippetsHelper do end def copy_button(blob_id) - "<button class=\"gl-button btn btn-default copy-to-clipboard-btn\" title=\"Copy snippet contents\" onclick=\"copyToClipboard('.blob-content[data-blob-id="#{blob_id}"] > pre')\">#{external_snippet_icon('copy-to-clipboard')}</button>" + "<button title=\"Copy snippet contents\" onclick=\"copyToClipboard('.blob-content[data-blob-id="#{blob_id}"] > pre')\" type=\"button\" class=\"gl-button btn btn-md btn-default \"><span class=\"gl-button-text\">\n#{external_snippet_icon('copy-to-clipboard')}\n</span>\n\n</button>" end end diff --git a/spec/helpers/time_helper_spec.rb b/spec/helpers/time_helper_spec.rb index 3e406f5e74e..02e28b2ba05 100644 --- a/spec/helpers/time_helper_spec.rb +++ b/spec/helpers/time_helper_spec.rb @@ -11,7 +11,7 @@ RSpec.describe TimeHelper do 100.32 => "1 minute and 40 seconds", 120 => "2 minutes", 121 => "2 minutes and 1 second", - 3721 => "62 minutes and 1 second", + 3721 => "1 hour, 2 minutes, and 1 second", 0 => "0 seconds" } diff --git a/spec/helpers/todos_helper_spec.rb b/spec/helpers/todos_helper_spec.rb index 9cbcca69dc8..dfb5cb995bc 100644 --- a/spec/helpers/todos_helper_spec.rb +++ b/spec/helpers/todos_helper_spec.rb @@ -370,6 +370,7 @@ RSpec.describe TodosHelper do Todo::APPROVAL_REQUIRED | false | format(s_("Todos|set %{who} as an approver"), who: _('you')) Todo::UNMERGEABLE | true | s_('Todos|Could not merge') Todo::MERGE_TRAIN_REMOVED | true | s_("Todos|Removed from Merge Train") + Todo::REVIEW_SUBMITTED | false | s_('Todos|reviewed your merge request') end with_them do diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb index 1ca5b8eb954..c94844eebbc 100644 --- a/spec/helpers/tree_helper_spec.rb +++ b/spec/helpers/tree_helper_spec.rb @@ -21,12 +21,14 @@ RSpec.describe TreeHelper do describe '#vue_file_list_data' do it 'returns a list of attributes related to the project' do + helper.instance_variable_set(:@ref_type, 'heads') expect(helper.vue_file_list_data(project, sha)).to include( project_path: project.full_path, project_short_path: project.path, ref: sha, escaped_ref: sha, - full_name: project.name_with_namespace + full_name: project.name_with_namespace, + ref_type: 'heads' ) end end diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb index c0d3c31a36d..ad8aef276bb 100644 --- a/spec/helpers/users_helper_spec.rb +++ b/spec/helpers/users_helper_spec.rb @@ -150,6 +150,76 @@ RSpec.describe UsersHelper do end end + describe '#can_impersonate_user' do + let(:user) { create(:user) } + let(:impersonation_in_progress) { false } + + subject { helper.can_impersonate_user(user, impersonation_in_progress) } + + context 'when password is expired' do + let(:user) { create(:user, password_expires_at: 1.minute.ago) } + + it { is_expected.to be false } + end + + context 'when impersonation is in progress' do + let(:impersonation_in_progress) { true } + + it { is_expected.to be false } + end + + context 'when user is blocked' do + let(:user) { create(:user, :blocked) } + + it { is_expected.to be false } + end + + context 'when user is internal' do + let(:user) { create(:user, :bot) } + + it { is_expected.to be false } + end + + it { is_expected.to be true } + end + + describe '#impersonation_error_text' do + let(:user) { create(:user) } + let(:impersonation_in_progress) { false } + + subject { helper.impersonation_error_text(user, impersonation_in_progress) } + + context 'when password is expired' do + let(:user) { create(:user, password_expires_at: 1.minute.ago) } + + it { is_expected.to eq(_("You cannot impersonate a user with an expired password")) } + end + + context 'when impersonation is in progress' do + let(:impersonation_in_progress) { true } + + it { is_expected.to eq(_("You are already impersonating another user")) } + end + + context 'when user is blocked' do + let(:user) { create(:user, :blocked) } + + it { is_expected.to eq(_("You cannot impersonate a blocked user")) } + end + + context 'when user is internal' do + let(:user) { create(:user, :bot) } + + it { is_expected.to eq(_("You cannot impersonate an internal user")) } + end + + context 'when user is inactive' do + let(:user) { create(:user, :deactivated) } + + it { is_expected.to eq(_("You cannot impersonate a user who cannot log in")) } + end + end + describe '#user_badges_in_admin_section' do before do allow(helper).to receive(:current_user).and_return(user) @@ -534,7 +604,7 @@ RSpec.describe UsersHelper do describe '#load_max_project_member_accesses' do let_it_be(:projects) { create_list(:project, 3) } - before(:all) do + before_all do projects.first.add_developer(user) end @@ -612,4 +682,58 @@ RSpec.describe UsersHelper do it { is_expected.to eq('Active') } end end + + describe '#user_profile_actions_data' do + let(:user_1) { create(:user) } + let(:user_2) { create(:user) } + let(:user_path) { '/users/root' } + + subject { helper.user_profile_actions_data(user_1) } + + before do + allow(helper).to receive(:user_path).and_return(user_path) + allow(helper).to receive(:user_url).and_return(user_path) + end + + shared_examples 'user cannot report' do + it 'returns data without reporting related data' do + is_expected.to match({ + user_id: user_1.id, + rss_subscription_path: user_path + }) + end + end + + context 'user is current user' do + before do + allow(helper).to receive(:current_user).and_return(user_1) + end + + it_behaves_like 'user cannot report' + end + + context 'user is not current user' do + before do + allow(helper).to receive(:current_user).and_return(user_2) + end + + it 'returns data for reporting related data' do + is_expected.to match({ + user_id: user_1.id, + rss_subscription_path: user_path, + report_abuse_path: add_category_abuse_reports_path, + reported_user_id: user_1.id, + reported_from_url: user_path + }) + end + end + + context 'when logged out' do + before do + allow(helper).to receive(:current_user).and_return(nil) + end + + it_behaves_like 'user cannot report' + end + end end |