diff options
Diffstat (limited to 'spec/features')
110 files changed, 1994 insertions, 635 deletions
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb index 85f0c44ed9c..166fde0f37a 100644 --- a/spec/features/admin/admin_builds_spec.rb +++ b/spec/features/admin/admin_builds_spec.rb @@ -12,7 +12,7 @@ RSpec.describe 'Admin Builds' do context 'All tab' do context 'when have jobs' do - it 'shows all jobs' do + it 'shows all jobs', :js do create(:ci_build, pipeline: pipeline, status: :pending) create(:ci_build, pipeline: pipeline, status: :running) create(:ci_build, pipeline: pipeline, status: :success) @@ -24,6 +24,10 @@ RSpec.describe 'Admin Builds' do expect(page).to have_selector('.row-content-block', text: 'All jobs') expect(page.all('.build-link').size).to eq(4) expect(page).to have_button 'Stop all jobs' + + click_button 'Stop all jobs' + expect(page).to have_button 'Stop jobs' + expect(page).to have_content 'Stop all jobs?' end end diff --git a/spec/features/admin/admin_dev_ops_report_spec.rb b/spec/features/admin/admin_dev_ops_report_spec.rb index c201011cbea..3b2c9d75870 100644 --- a/spec/features/admin/admin_dev_ops_report_spec.rb +++ b/spec/features/admin/admin_dev_ops_report_spec.rb @@ -2,59 +2,65 @@ require 'spec_helper' -RSpec.describe 'DevOps Report page' do +RSpec.describe 'DevOps Report page', :js do before do sign_in(create(:admin)) end - it 'has dismissable intro callout', :js do - visit admin_dev_ops_report_path + context 'with devops_adoption feature flag disabled' do + before do + stub_feature_flags(devops_adoption: false) + end - expect(page).to have_content 'Introducing Your DevOps Report' + it 'has dismissable intro callout' do + visit admin_dev_ops_report_path - find('.js-close-callout').click + expect(page).to have_content 'Introducing Your DevOps Report' - expect(page).not_to have_content 'Introducing Your DevOps Report' - end + find('.js-close-callout').click - context 'when usage ping is disabled' do - before do - stub_application_setting(usage_ping_enabled: false) + expect(page).not_to have_content 'Introducing Your DevOps Report' end - it 'shows empty state', :js do - visit admin_dev_ops_report_path + context 'when usage ping is disabled' do + before do + stub_application_setting(usage_ping_enabled: false) + end - expect(page).to have_selector(".js-empty-state") - end + it 'shows empty state' do + visit admin_dev_ops_report_path - it 'hides the intro callout' do - visit admin_dev_ops_report_path + expect(page).to have_selector(".js-empty-state") + end - expect(page).not_to have_content 'Introducing Your DevOps Report' + it 'hides the intro callout' do + visit admin_dev_ops_report_path + + expect(page).not_to have_content 'Introducing Your DevOps Report' + end end - end - context 'when there is no data to display' do - it 'shows empty state' do - stub_application_setting(usage_ping_enabled: true) + context 'when there is no data to display' do + it 'shows empty state' do + stub_application_setting(usage_ping_enabled: true) - visit admin_dev_ops_report_path + visit admin_dev_ops_report_path - expect(page).to have_content('Data is still calculating') + expect(page).to have_content('Data is still calculating') + end end - end - context 'when there is data to display' do - it 'shows numbers for each metric' do - stub_application_setting(usage_ping_enabled: true) - create(:dev_ops_report_metric) + context 'when there is data to display' do + it 'shows numbers for each metric' do + stub_application_setting(usage_ping_enabled: true) + create(:dev_ops_report_metric) - visit admin_dev_ops_report_path + visit admin_dev_ops_report_path - expect(page).to have_content( - 'Issues created per active user 1.2 You 9.3 Lead 13.3%' - ) + expect(page).to have_content( + 'Issues created per active user 1.2 You 9.3 Lead 13.3%' + ) + end end end end diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb index 653a45a4bb8..96709cf8a12 100644 --- a/spec/features/admin/admin_groups_spec.rb +++ b/spec/features/admin/admin_groups_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe 'Admin Groups' do include Select2Helper + include Spec::Support::Helpers::Features::MembersHelpers let(:internal) { Gitlab::VisibilityLevel::INTERNAL } let(:user) { create :user } @@ -11,8 +12,6 @@ RSpec.describe 'Admin Groups' do let!(:current_user) { create(:admin) } before do - stub_feature_flags(vue_group_members_list: false) - sign_in(current_user) stub_application_setting(default_group_visibility: internal) end @@ -176,7 +175,7 @@ RSpec.describe 'Admin Groups' do click_button 'Invite' - page.within '[data-qa-selector="members_list"]' do + page.within members_table do expect(page).to have_content(current_user.name) expect(page).to have_content('Developer') end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 528dfad606e..8929abc7edc 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -132,32 +132,14 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n context 'Change Sign-up restrictions' do context 'Require Admin approval for new signup setting' do - context 'when feature is enabled' do - before do - stub_feature_flags(admin_approval_for_new_user_signups: true) - end - - it 'changes the setting' do - page.within('.as-signup') do - check 'Require admin approval for new sign-ups' - click_button 'Save changes' - end - - expect(current_settings.require_admin_approval_after_user_signup).to be_truthy - expect(page).to have_content "Application settings saved successfully" - end - end - - context 'when feature is disabled' do - before do - stub_feature_flags(admin_approval_for_new_user_signups: false) + it 'changes the setting' do + page.within('.as-signup') do + check 'Require admin approval for new sign-ups' + click_button 'Save changes' end - it 'does not show the the setting' do - page.within('.as-signup') do - expect(page).not_to have_selector('.application_setting_require_admin_approval_after_user_signup') - end - end + expect(current_settings.require_admin_approval_after_user_signup).to be_truthy + expect(page).to have_content "Application settings saved successfully" end end end diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index e06e2d14f3c..97a30143a59 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -75,26 +75,12 @@ RSpec.describe "Admin::Users" do end context '`Pending approval` tab' do - context 'feature is enabled' do - before do - stub_feature_flags(admin_approval_for_new_user_signups: true) - visit admin_users_path - end - - it 'shows the `Pending approval` tab' do - expect(page).to have_link('Pending approval', href: admin_users_path(filter: 'blocked_pending_approval')) - end + before do + visit admin_users_path end - context 'feature is disabled' do - before do - stub_feature_flags(admin_approval_for_new_user_signups: false) - visit admin_users_path - end - - it 'does not show the `Pending approval` tab' do - expect(page).not_to have_link('Pending approval', href: admin_users_path(filter: 'blocked_pending_approval')) - end + it 'shows the `Pending approval` tab' do + expect(page).to have_link('Pending approval', href: admin_users_path(filter: 'blocked_pending_approval')) end end end @@ -218,6 +204,32 @@ RSpec.describe "Admin::Users" do expect(page).to have_content(user.email) end end + + context 'when blocking a user' do + it 'shows confirmation and allows blocking', :js do + expect(page).to have_content(user.email) + + find("[data-testid='user-action-button-#{user.id}']").click + + within find("[data-testid='user-action-dropdown-#{user.id}']") do + find('li button', text: 'Block').click + end + + wait_for_requests + + expect(page).to have_content('Block user') + expect(page).to have_content('Blocking user has the following effects') + expect(page).to have_content('User will not be able to login') + expect(page).to have_content('Owned groups will be left') + + find('.modal-footer button', text: 'Block').click + + wait_for_requests + + expect(page).to have_content('Successfully blocked') + expect(page).not_to have_content(user.email) + end + end end describe "GET /admin/users/new" do @@ -376,6 +388,26 @@ RSpec.describe "Admin::Users" do end end + context 'when blocking the user' do + it 'shows confirmation and allows blocking', :js do + visit admin_user_path(user) + + find('button', text: 'Block user').click + + wait_for_requests + + expect(page).to have_content('Block user') + expect(page).to have_content('You can always unblock their account, their data will remain intact.') + + find('.modal-footer button', text: 'Block').click + + wait_for_requests + + expect(page).to have_content('Successfully blocked') + expect(page).to have_content('This user is blocked') + end + end + describe 'Impersonation' do let(:another_user) { create(:user) } diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb index 44642983a36..0fb5124f673 100644 --- a/spec/features/admin/admin_uses_repository_checks_spec.rb +++ b/spec/features/admin/admin_uses_repository_checks_spec.rb @@ -46,7 +46,7 @@ RSpec.describe 'Admin uses repository checks', :request_store, :clean_gitlab_red ) visit_admin_project_page(project) - page.within('.gl-alert') do + page.within('[data-testid="last-repository-check-failed-alert"]') do expect(page.text).to match(/Last repository check \(just now\) failed/) end end diff --git a/spec/features/alert_management_spec.rb b/spec/features/alert_management_spec.rb index 2989f72e356..3322c9c574f 100644 --- a/spec/features/alert_management_spec.rb +++ b/spec/features/alert_management_spec.rb @@ -41,21 +41,6 @@ RSpec.describe 'Alert management', :js do expect(page).to have_content(environment.name) end end - - context 'when expose_environment_path_in_alert_details feature flag is disabled' do - before do - stub_feature_flags(expose_environment_path_in_alert_details: false) - end - - it 'does not show the environment name' do - visit(details_project_alert_management_path(project, alert)) - - within('.alert-management-details-table') do - expect(page).to have_content(alert.title) - expect(page).not_to have_content(environment.name) - end - end - end end end end diff --git a/spec/features/alerts_settings/user_views_alerts_settings_spec.rb b/spec/features/alerts_settings/user_views_alerts_settings_spec.rb new file mode 100644 index 00000000000..0ded13ae607 --- /dev/null +++ b/spec/features/alerts_settings/user_views_alerts_settings_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Alert integrations settings form', :js do + let_it_be(:project) { create(:project) } + let_it_be(:maintainer) { create(:user) } + let_it_be(:developer) { create(:user) } + + before_all do + project.add_maintainer(maintainer) + project.add_developer(developer) + end + + before do + sign_in(maintainer) + end + + describe 'when viewing alert integrations as a maintainer' do + context 'with feature flag enabled' do + before do + visit project_settings_operations_path(project, anchor: 'js-alert-management-settings') + wait_for_requests + end + + it 'shows the alerts setting form title' do + page.within('#js-alert-management-settings') do + expect(find('h3')).to have_content('Alerts') + end + end + + it 'shows the new alerts setting form' do + expect(page).to have_content('1. Select integration type') + end + end + + context 'with feature flag disabled' do + before do + stub_feature_flags(http_integrations_list: false) + + visit project_settings_operations_path(project, anchor: 'js-alert-management-settings') + wait_for_requests + end + + it 'shows the old alerts setting form' do + expect(page).to have_content('Webhook URL') + end + end + end + + describe 'when viewing alert integrations as a developer' do + before do + sign_in(developer) + + visit project_settings_operations_path(project, anchor: 'js-alert-management-settings') + wait_for_requests + end + + it 'shows the old alerts setting form' do + expect(page).not_to have_selector('.incident-management-list') + expect(page).not_to have_selector('#js-alert-management-settings') + end + end +end diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb index 00efca5d3a8..f941adca233 100644 --- a/spec/features/boards/add_issues_modal_spec.rb +++ b/spec/features/boards/add_issues_modal_spec.rb @@ -87,11 +87,12 @@ RSpec.describe 'Issue Boards add issue modal', :js do end end - it 'shows selected issues' do + it 'shows selected issues tab and empty state message' do page.within('.add-issues-modal') do click_link 'Selected issues' expect(page).not_to have_selector('.board-card') + expect(page).to have_content("Go back to Open issues and select some issues to add to your board.") end end @@ -147,7 +148,7 @@ RSpec.describe 'Issue Boards add issue modal', :js do end end - context 'selecing issues' do + context 'selecting issues' do it 'selects single issue' do page.within('.add-issues-modal') do first('.board-card .board-card-number').click @@ -206,7 +207,7 @@ RSpec.describe 'Issue Boards add issue modal', :js do end end - it 'selects all that arent already selected' do + it "selects all that aren't already selected" do page.within('.add-issues-modal') do first('.board-card .board-card-number').click diff --git a/spec/features/breadcrumbs_schema_markup_spec.rb b/spec/features/breadcrumbs_schema_markup_spec.rb new file mode 100644 index 00000000000..30d5f40fea8 --- /dev/null +++ b/spec/features/breadcrumbs_schema_markup_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Breadcrumbs schema markup', :aggregate_failures do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public, namespace: user.namespace) } + let_it_be(:issue) { create(:issue, project: project) } + let_it_be(:group) { create(:group, :public) } + let_it_be(:subgroup) { create(:group, :public, parent: group) } + let_it_be(:group_project) { create(:project, :public, namespace: subgroup) } + + it 'generates the breadcrumb schema for user projects' do + visit project_url(project) + + item_list = get_schema_content + + expect(item_list.size).to eq 3 + expect(item_list[0]['name']).to eq project.namespace.name + expect(item_list[0]['item']).to eq user_url(project.owner) + + expect(item_list[1]['name']).to eq project.name + expect(item_list[1]['item']).to eq project_url(project) + + expect(item_list[2]['name']).to eq 'Details' + expect(item_list[2]['item']).to eq project_url(project) + end + + it 'generates the breadcrumb schema for group projects' do + visit project_url(group_project) + + item_list = get_schema_content + + expect(item_list.size).to eq 4 + expect(item_list[0]['name']).to eq group.name + expect(item_list[0]['item']).to eq group_url(group) + + expect(item_list[1]['name']).to eq subgroup.name + expect(item_list[1]['item']).to eq group_url(subgroup) + + expect(item_list[2]['name']).to eq group_project.name + expect(item_list[2]['item']).to eq project_url(group_project) + + expect(item_list[3]['name']).to eq 'Details' + expect(item_list[3]['item']).to eq project_url(group_project) + end + + it 'generates the breadcrumb schema for group' do + visit group_url(subgroup) + + item_list = get_schema_content + + expect(item_list.size).to eq 3 + expect(item_list[0]['name']).to eq group.name + expect(item_list[0]['item']).to eq group_url(group) + + expect(item_list[1]['name']).to eq subgroup.name + expect(item_list[1]['item']).to eq group_url(subgroup) + + expect(item_list[2]['name']).to eq 'Details' + expect(item_list[2]['item']).to eq group_url(subgroup) + end + + it 'generates the breadcrumb schema for issues' do + visit project_issues_url(project) + + item_list = get_schema_content + + expect(item_list.size).to eq 3 + expect(item_list[0]['name']).to eq project.namespace.name + expect(item_list[0]['item']).to eq user_url(project.owner) + + expect(item_list[1]['name']).to eq project.name + expect(item_list[1]['item']).to eq project_url(project) + + expect(item_list[2]['name']).to eq 'Issues' + expect(item_list[2]['item']).to eq project_issues_url(project) + end + + it 'generates the breadcrumb schema for specific issue' do + visit project_issue_url(project, issue) + + item_list = get_schema_content + + expect(item_list.size).to eq 4 + expect(item_list[0]['name']).to eq project.namespace.name + expect(item_list[0]['item']).to eq user_url(project.owner) + + expect(item_list[1]['name']).to eq project.name + expect(item_list[1]['item']).to eq project_url(project) + + expect(item_list[2]['name']).to eq 'Issues' + expect(item_list[2]['item']).to eq project_issues_url(project) + + expect(item_list[3]['name']).to eq issue.to_reference + expect(item_list[3]['item']).to eq project_issue_url(project, issue) + end + + def get_schema_content + content = find('script[type="application/ld+json"]', visible: false).text(:all) + + expect(content).not_to be_nil + + Gitlab::Json.parse(content)['itemListElement'] + end +end diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb index 5f58fa420fb..60d485d4558 100644 --- a/spec/features/calendar_spec.rb +++ b/spec/features/calendar_spec.rb @@ -42,7 +42,7 @@ RSpec.describe 'Contributions Calendar', :js do "#{contributions} #{'contribution'.pluralize(contributions)}" end - "#{get_cell_color_selector(contributions)}[data-original-title='#{contribution_text}<br />#{date}']" + "#{get_cell_color_selector(contributions)}[title='#{contribution_text}<br />#{date}']" end def push_code_contribution diff --git a/spec/features/callouts/registration_enabled_spec.rb b/spec/features/callouts/registration_enabled_spec.rb new file mode 100644 index 00000000000..4055965273f --- /dev/null +++ b/spec/features/callouts/registration_enabled_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Registration enabled callout' do + let_it_be(:admin) { create(:admin) } + let_it_be(:non_admin) { create(:user) } + + context 'when "Sign-up enabled" setting is `true`' do + before do + stub_application_setting(signup_enabled: true) + end + + context 'when an admin is logged in' do + before do + sign_in(admin) + visit root_dashboard_path + end + + it 'displays callout' do + expect(page).to have_content 'Open registration is enabled on your instance.' + expect(page).to have_link 'View setting', href: general_admin_application_settings_path(anchor: 'js-signup-settings') + end + + context 'when callout is dismissed', :js do + before do + find('[data-testid="close-registration-enabled-callout"]').click + + visit root_dashboard_path + end + + it 'does not display callout' do + expect(page).not_to have_content 'Open registration is enabled on your instance.' + end + end + end + + context 'when a non-admin is logged in' do + before do + sign_in(non_admin) + visit root_dashboard_path + end + + it 'does not display callout' do + expect(page).not_to have_content 'Open registration is enabled on your instance.' + end + end + end +end diff --git a/spec/features/canonical_link_spec.rb b/spec/features/canonical_link_spec.rb new file mode 100644 index 00000000000..8b64e9a5b9d --- /dev/null +++ b/spec/features/canonical_link_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Canonical link' do + include Spec::Support::Helpers::Features::CanonicalLinkHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public, namespace: user.namespace) } + let_it_be(:issue) { create(:issue, project: project) } + + let_it_be(:issue_request) { issue_url(issue) } + let_it_be(:project_request) { project_url(project) } + + before do + sign_in(user) + end + + shared_examples 'shows canonical link' do + specify do + visit request_url + + expect(page).to have_canonical_link(expected_url) + end + end + + shared_examples 'does not show canonical link' do + specify do + visit request_url + + expect(page).not_to have_any_canonical_links + end + end + + it_behaves_like 'does not show canonical link' do + let(:request_url) { issue_request } + end + + it_behaves_like 'shows canonical link' do + let(:request_url) { issue_request + '/' } + let(:expected_url) { issue_request } + end + + it_behaves_like 'shows canonical link' do + let(:request_url) { project_issues_url(project) + "/?state=opened" } + let(:expected_url) { project_issues_url(project, state: 'opened') } + end + + it_behaves_like 'does not show canonical link' do + let(:request_url) { project_request } + end + + it_behaves_like 'shows canonical link' do + let(:request_url) { project_request + '/' } + let(:expected_url) { project_request } + end + + it_behaves_like 'shows canonical link' do + let(:query_params) { '?foo=bar' } + let(:request_url) { project_request + "/#{query_params}" } + let(:expected_url) { project_request + query_params } + end + + # Hard-coded canonical links + + it_behaves_like 'shows canonical link' do + let(:request_url) { explore_root_path } + let(:expected_url) { explore_projects_url } + end +end diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb index a3eacd6147c..c14a6001a3e 100644 --- a/spec/features/dashboard/datetime_on_tooltips_spec.rb +++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb @@ -49,7 +49,7 @@ RSpec.describe 'Tooltips on .timeago dates', :js do end def datetime_in_tooltip - datetime_text = page.find('.local-timeago').text + datetime_text = page.find('.tooltip').text DateTime.parse(datetime_text) end end diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb index 04bbc3059de..58352518d43 100644 --- a/spec/features/dashboard/shortcuts_spec.rb +++ b/spec/features/dashboard/shortcuts_spec.rb @@ -59,7 +59,7 @@ RSpec.describe 'Dashboard shortcuts', :js do find('body').send_keys([:shift, 'P']) find('.nothing-here-block') - expect(page).to have_content("This user doesn't have any personal projects") + expect(page).to have_content('Explore public groups to find projects to contribute to.') end end diff --git a/spec/features/discussion_comments/merge_request_spec.rb b/spec/features/discussion_comments/merge_request_spec.rb index 43801b30608..761cc7ae796 100644 --- a/spec/features/discussion_comments/merge_request_spec.rb +++ b/spec/features/discussion_comments/merge_request_spec.rb @@ -8,6 +8,8 @@ RSpec.describe 'Thread Comments Merge Request', :js do let(:merge_request) { create(:merge_request, source_project: project) } before do + stub_feature_flags(remove_resolve_note: false) + project.add_maintainer(user) sign_in(user) diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb index 49343cc7a57..0912df22924 100644 --- a/spec/features/expand_collapse_diffs_spec.rb +++ b/spec/features/expand_collapse_diffs_spec.rb @@ -8,6 +8,8 @@ RSpec.describe 'Expand and collapse diffs', :js do before do stub_feature_flags(increased_diff_limits: false) + allow(Gitlab::CurrentSettings).to receive(:diff_max_patch_bytes).and_return(100.kilobytes) + sign_in(create(:admin)) # Ensure that undiffable.md is in .gitattributes diff --git a/spec/features/explore/user_explores_projects_spec.rb b/spec/features/explore/user_explores_projects_spec.rb index e217638f62b..bf4d6c946e1 100644 --- a/spec/features/explore/user_explores_projects_spec.rb +++ b/spec/features/explore/user_explores_projects_spec.rb @@ -3,65 +3,112 @@ require 'spec_helper' RSpec.describe 'User explores projects' do - let_it_be(:archived_project) { create(:project, :archived) } - let_it_be(:internal_project) { create(:project, :internal) } - let_it_be(:private_project) { create(:project, :private) } - let_it_be(:public_project) { create(:project, :public) } - - context 'when not signed in' do - context 'when viewing public projects' do - before do - visit(explore_projects_path) + context 'when some projects exist' do + let_it_be(:archived_project) { create(:project, :archived) } + let_it_be(:internal_project) { create(:project, :internal) } + let_it_be(:private_project) { create(:project, :private) } + let_it_be(:public_project) { create(:project, :public) } + + context 'when not signed in' do + context 'when viewing public projects' do + before do + visit(explore_projects_path) + end + + include_examples 'shows public projects' end - include_examples 'shows public projects' + context 'when visibility is restricted to public' do + before do + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + visit(explore_projects_path) + end + + it 'redirects to login page' do + expect(page).to have_current_path(new_user_session_path) + end + end end - context 'when visibility is restricted to public' do + context 'when signed in' do + let_it_be(:user) { create(:user) } + before do - stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) - visit(explore_projects_path) + sign_in(user) + + stub_feature_flags(project_list_filter_bar: false) + end + + shared_examples 'empty search results' do + it 'shows correct empty state message', :js do + fill_in 'name', with: 'zzzzzzzzzzzzzzzzzzz' + + expect(page).to have_content('Explore public groups to find projects to contribute to.') + end end - it 'redirects to login page' do - expect(page).to have_current_path(new_user_session_path) + context 'when viewing public projects' do + before do + visit(explore_projects_path) + end + + include_examples 'shows public and internal projects' + include_examples 'empty search results' + end + + context 'when viewing most starred projects' do + before do + visit(starred_explore_projects_path) + end + + include_examples 'shows public and internal projects' + include_examples 'empty search results' + end + + context 'when viewing trending projects' do + before do + [archived_project, public_project].each { |project| create(:note_on_issue, project: project) } + + TrendingProject.refresh! + + visit(trending_explore_projects_path) + end + + include_examples 'shows public projects' + include_examples 'empty search results' end end end - context 'when signed in' do - let_it_be(:user) { create(:user) } - - before do - sign_in(user) + context 'when there are no projects' do + shared_examples 'explore page empty state' do + it 'shows correct empty state message' do + expect(page).to have_content('Explore public groups to find projects to contribute to.') + end end context 'when viewing public projects' do before do - visit(explore_projects_path) + visit explore_projects_path end - include_examples 'shows public and internal projects' + it_behaves_like 'explore page empty state' end context 'when viewing most starred projects' do before do - visit(starred_explore_projects_path) + visit starred_explore_projects_path end - include_examples 'shows public and internal projects' + it_behaves_like 'explore page empty state' end context 'when viewing trending projects' do before do - [archived_project, public_project].each { |project| create(:note_on_issue, project: project) } - - TrendingProject.refresh! - - visit(trending_explore_projects_path) + visit trending_explore_projects_path end - include_examples 'shows public projects' + it_behaves_like 'explore page empty state' end end end diff --git a/spec/features/file_uploads/multipart_invalid_uploads_spec.rb b/spec/features/file_uploads/multipart_invalid_uploads_spec.rb index e9e24c12af1..b3ace2e30ff 100644 --- a/spec/features/file_uploads/multipart_invalid_uploads_spec.rb +++ b/spec/features/file_uploads/multipart_invalid_uploads_spec.rb @@ -22,13 +22,13 @@ RSpec.describe 'Invalid uploads that must be rejected', :api, :js do ) end - RSpec.shared_examples 'rejecting invalid keys' do |key_name:, message: nil| + RSpec.shared_examples 'rejecting invalid keys' do |key_name:, message: nil, status: 500| context "with invalid key #{key_name}" do let(:body) { { key_name => file, 'package[test][name]' => 'test' } } it { expect { subject }.not_to change { Packages::Package.nuget.count } } - it { expect(subject.code).to eq(500) } + it { expect(subject.code).to eq(status) } it { expect(subject.body).to include(message.presence || "invalid field: \"#{key_name}\"") } end @@ -45,7 +45,7 @@ RSpec.describe 'Invalid uploads that must be rejected', :api, :js do # These keys are rejected directly by rack itself. # The request will not be received by multipart.rb (can't use the 'handling file uploads' shared example) it_behaves_like 'rejecting invalid keys', key_name: 'x' * 11000, message: 'Puma caught this error: exceeded available parameter key space (RangeError)' - it_behaves_like 'rejecting invalid keys', key_name: 'package[]test', message: 'Puma caught this error: expected Hash (got Array)' + it_behaves_like 'rejecting invalid keys', key_name: 'package[]test', status: 400, message: 'Bad Request' it_behaves_like 'handling file uploads', 'by rejecting uploads with an invalid key' end diff --git a/spec/features/frequently_visited_projects_and_groups_spec.rb b/spec/features/frequently_visited_projects_and_groups_spec.rb new file mode 100644 index 00000000000..b8797d9c139 --- /dev/null +++ b/spec/features/frequently_visited_projects_and_groups_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Frequently visited items', :js do + let_it_be(:user) { create(:user) } + + before do + sign_in(user) + end + + context 'for projects' do + let_it_be(:project) { create(:project, :public) } + + it 'increments localStorage counter when visiting the project' do + visit project_path(project) + + frequent_projects = nil + + wait_for('localStorage frequent-projects') do + frequent_projects = page.evaluate_script("localStorage['#{user.username}/frequent-projects']") + + frequent_projects.present? + end + + expect(Gitlab::Json.parse(frequent_projects)).to contain_exactly(a_hash_including('id' => project.id, 'frequency' => 1)) + end + end + + context 'for groups' do + let_it_be(:group) { create(:group, :public) } + + it 'increments localStorage counter when visiting the group' do + visit group_path(group) + + frequent_groups = nil + + wait_for('localStorage frequent-groups') do + frequent_groups = page.evaluate_script("localStorage['#{user.username}/frequent-groups']") + + frequent_groups.present? + end + + expect(Gitlab::Json.parse(frequent_groups)).to contain_exactly(a_hash_including('id' => group.id, 'frequency' => 1)) + end + end +end diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb index 0ca626381d4..e6e4a55c1bb 100644 --- a/spec/features/global_search_spec.rb +++ b/spec/features/global_search_spec.rb @@ -36,15 +36,15 @@ RSpec.describe 'Global search' do end end - it 'closes the dropdown on blur', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/201841' do + it 'closes the dropdown on blur', :js do + find('#search').click fill_in 'search', with: "a" - dropdown = find('.js-dashboard-search-options') - expect(dropdown[:class]).to include 'show' + expect(page).to have_selector("div[data-testid='dashboard-search-options'].show") find('#search').send_keys(:backspace) find('body').click - expect(dropdown[:class]).not_to include 'show' + expect(page).to have_no_selector("div[data-testid='dashboard-search-options'].show") end end diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb index 9a3dca61680..c7d37205b71 100644 --- a/spec/features/group_variables_spec.rb +++ b/spec/features/group_variables_spec.rb @@ -11,7 +11,7 @@ RSpec.describe 'Group variables', :js do before do group.add_owner(user) gitlab_sign_in(user) - stub_feature_flags(new_variables_ui: false) + wait_for_requests visit page_path end diff --git a/spec/features/groups/container_registry_spec.rb b/spec/features/groups/container_registry_spec.rb index acac8724edf..1b23b8b4bf9 100644 --- a/spec/features/groups/container_registry_spec.rb +++ b/spec/features/groups/container_registry_spec.rb @@ -89,6 +89,20 @@ RSpec.describe 'Container Registry', :js do end end + context 'when an image has the same name as the subgroup' do + before do + stub_container_registry_tags(tags: %w[latest], with_manifest: true) + project.container_repositories << create(:container_repository, name: group.name) + visit_container_registry + end + + it 'details page loads properly' do + find('a[data-testid="details-link"]').click + + expect(page).to have_content 'latest' + end + end + def visit_container_registry visit group_container_registries_path(group) end diff --git a/spec/features/groups/dependency_proxy_spec.rb b/spec/features/groups/dependency_proxy_spec.rb new file mode 100644 index 00000000000..9bbfdc488fb --- /dev/null +++ b/spec/features/groups/dependency_proxy_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Group Dependency Proxy' do + let(:developer) { create(:user) } + let(:reporter) { create(:user) } + let(:group) { create(:group) } + let(:path) { group_dependency_proxy_path(group) } + + before do + group.add_developer(developer) + group.add_reporter(reporter) + + enable_feature + end + + describe 'feature settings' do + context 'when not logged in and feature disabled' do + it 'does not show the feature settings' do + group.create_dependency_proxy_setting(enabled: false) + + visit path + + expect(page).not_to have_css('.js-dependency-proxy-toggle-area') + expect(page).not_to have_css('.js-dependency-proxy-url') + end + end + + context 'feature is available', :js do + context 'when logged in as group developer' do + before do + sign_in(developer) + visit path + end + + it 'sidebar menu is open' do + sidebar = find('.nav-sidebar') + expect(sidebar).to have_link _('Dependency Proxy') + end + + it 'toggles defaults to enabled' do + page.within('.js-dependency-proxy-toggle-area') do + expect(find('.js-project-feature-toggle-input', visible: false).value).to eq('true') + end + end + + it 'shows the proxy URL' do + page.within('.edit_dependency_proxy_group_setting') do + expect(find('.js-dependency-proxy-url').value).to have_content('/dependency_proxy/containers') + end + end + + it 'hides the proxy URL when feature is disabled' do + page.within('.edit_dependency_proxy_group_setting') do + find('.js-project-feature-toggle').click + end + + expect(page).not_to have_css('.js-dependency-proxy-url') + expect(find('.js-project-feature-toggle-input', visible: false).value).to eq('false') + end + end + + context 'when logged in as group reporter' do + before do + sign_in(reporter) + visit path + end + + it 'does not show the feature toggle but shows the proxy URL' do + expect(page).not_to have_css('.js-dependency-proxy-toggle-area') + expect(find('.js-dependency-proxy-url').value).to have_content('/dependency_proxy/containers') + end + end + end + + context 'feature is not avaible' do + before do + sign_in(developer) + end + + context 'group is private' do + let(:group) { create(:group, :private) } + + it 'informs user that feature is only available for public groups' do + visit path + + expect(page).to have_content('Dependency proxy feature is limited to public groups for now.') + end + end + + context 'feature is disabled globally' do + it 'renders 404 page' do + disable_feature + + visit path + + expect(page).to have_gitlab_http_status(:not_found) + end + end + end + end + + def enable_feature + stub_config(dependency_proxy: { enabled: true }) + end + + def disable_feature + stub_config(dependency_proxy: { enabled: false }) + end +end diff --git a/spec/features/groups/members/filter_members_spec.rb b/spec/features/groups/members/filter_members_spec.rb index d667690af29..b6d33b3f4aa 100644 --- a/spec/features/groups/members/filter_members_spec.rb +++ b/spec/features/groups/members/filter_members_spec.rb @@ -2,16 +2,19 @@ require 'spec_helper' -RSpec.describe 'Groups > Members > Filter members' do +RSpec.describe 'Groups > Members > Filter members', :js do + include Spec::Support::Helpers::Features::MembersHelpers + let(:user) { create(:user) } let(:nested_group_user) { create(:user) } let(:user_with_2fa) { create(:user, :two_factor_via_otp) } let(:group) { create(:group) } let(:nested_group) { create(:group, parent: group) } - before do - stub_feature_flags(vue_group_members_list: false) + two_factor_auth_dropdown_toggle_selector = '[data-testid="member-filter-2fa-dropdown"] [data-testid="dropdown-toggle"]' + active_inherited_members_filter_selector = '[data-testid="filter-members-with-inherited-permissions"] a.is-active' + before do group.add_owner(user) group.add_maintainer(user_with_2fa) nested_group.add_maintainer(nested_group_user) @@ -24,23 +27,23 @@ RSpec.describe 'Groups > Members > Filter members' do expect(member(0)).to include(user.name) expect(member(1)).to include(user_with_2fa.name) - expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: 'Everyone') + expect(page).to have_css(two_factor_auth_dropdown_toggle_selector, text: 'Everyone') end it 'shows only 2FA members' do visit_members_list(group, two_factor: 'enabled') expect(member(0)).to include(user_with_2fa.name) - expect(members_list.size).to eq(1) - expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: 'Enabled') + expect(all_rows.size).to eq(1) + expect(page).to have_css(two_factor_auth_dropdown_toggle_selector, text: 'Enabled') end it 'shows only non 2FA members' do visit_members_list(group, two_factor: 'disabled') expect(member(0)).to include(user.name) - expect(members_list.size).to eq(1) - expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: 'Disabled') + expect(all_rows.size).to eq(1) + expect(page).to have_css(two_factor_auth_dropdown_toggle_selector, text: 'Disabled') end it 'shows inherited members by default' do @@ -49,35 +52,31 @@ RSpec.describe 'Groups > Members > Filter members' do expect(member(0)).to include(user.name) expect(member(1)).to include(user_with_2fa.name) expect(member(2)).to include(nested_group_user.name) - expect(members_list.size).to eq(3) + expect(all_rows.size).to eq(3) - expect(page).to have_css('[data-qa-selector="filter-members-with-inherited-permissions"] a.is-active', text: 'Show all members') + expect(page).to have_css(active_inherited_members_filter_selector, text: 'Show all members', visible: false) end it 'shows only group members' do visit_members_list(nested_group, with_inherited_permissions: 'exclude') expect(member(0)).to include(nested_group_user.name) - expect(members_list.size).to eq(1) - expect(page).to have_css('[data-qa-selector="filter-members-with-inherited-permissions"] a.is-active', text: 'Show only direct members') + expect(all_rows.size).to eq(1) + expect(page).to have_css(active_inherited_members_filter_selector, text: 'Show only direct members', visible: false) end it 'shows only inherited members' do visit_members_list(nested_group, with_inherited_permissions: 'only') expect(member(0)).to include(user.name) expect(member(1)).to include(user_with_2fa.name) - expect(members_list.size).to eq(2) - expect(page).to have_css('[data-qa-selector="filter-members-with-inherited-permissions"] a.is-active', text: 'Show only inherited members') + expect(all_rows.size).to eq(2) + expect(page).to have_css(active_inherited_members_filter_selector, text: 'Show only inherited members', visible: false) end def visit_members_list(group, options = {}) visit group_group_members_path(group.to_param, options) end - def members_list - page.all('ul.content-list > li') - end - def member(number) - members_list[number].text + all_rows[number].text end end diff --git a/spec/features/groups/members/leave_group_spec.rb b/spec/features/groups/members/leave_group_spec.rb index 32acf7edd2a..b73313745e9 100644 --- a/spec/features/groups/members/leave_group_spec.rb +++ b/spec/features/groups/members/leave_group_spec.rb @@ -3,14 +3,14 @@ require 'spec_helper' RSpec.describe 'Groups > Members > Leave group' do + include Spec::Support::Helpers::Features::MembersHelpers + let(:user) { create(:user) } let(:other_user) { create(:user) } let(:group) { create(:group) } before do - stub_feature_flags(vue_group_members_list: false) - - gitlab_sign_in(user) + sign_in(user) end it 'guest leaves the group' do @@ -61,7 +61,7 @@ RSpec.describe 'Groups > Members > Leave group' do expect(group.users).not_to include(user) end - it 'owner can not leave the group if they are the last owner' do + it 'owner can not leave the group if they are the last owner', :js do group.add_owner(user) visit group_path(group) @@ -70,7 +70,7 @@ RSpec.describe 'Groups > Members > Leave group' do visit group_group_members_path(group) - expect(find(:css, '.project-members-page li', text: user.name)).to have_no_selector(:css, 'a.btn-danger') + expect(members_table).not_to have_selector 'button[title="Leave"]' end it 'owner can not leave the group by url param if they are the last owner', :js do diff --git a/spec/features/groups/members/list_members_spec.rb b/spec/features/groups/members/list_members_spec.rb index bcec2b50a24..b0a896ec8cb 100644 --- a/spec/features/groups/members/list_members_spec.rb +++ b/spec/features/groups/members/list_members_spec.rb @@ -2,9 +2,8 @@ require 'spec_helper' -RSpec.describe 'Groups > Members > List members' do - include Select2Helper - include Spec::Support::Helpers::Features::ListRowsHelpers +RSpec.describe 'Groups > Members > List members', :js do + include Spec::Support::Helpers::Features::MembersHelpers let(:user1) { create(:user, name: 'John Doe') } let(:user2) { create(:user, name: 'Mary Jane') } @@ -12,8 +11,6 @@ RSpec.describe 'Groups > Members > List members' do let(:nested_group) { create(:group, parent: group) } before do - stub_feature_flags(vue_group_members_list: false) - sign_in(user1) end @@ -42,10 +39,12 @@ RSpec.describe 'Groups > Members > List members' do group.add_developer(user2) end - subject { visit group_group_members_path(group) } + it 'shows the status' do + create(:user_status, user: user2, emoji: 'smirk', message: 'Authoring this object') + + visit group_group_members_path(nested_group) - it_behaves_like 'showing user status' do - let(:user_with_status) { user2 } + expect(first_row).to have_selector('gl-emoji[data-name="smirk"]') end end end diff --git a/spec/features/groups/members/manage_groups_spec.rb b/spec/features/groups/members/manage_groups_spec.rb index 33caa3af36d..31a2c868cac 100644 --- a/spec/features/groups/members/manage_groups_spec.rb +++ b/spec/features/groups/members/manage_groups_spec.rb @@ -4,13 +4,11 @@ require 'spec_helper' RSpec.describe 'Groups > Members > Manage groups', :js do include Select2Helper - include Spec::Support::Helpers::Features::ListRowsHelpers + include Spec::Support::Helpers::Features::MembersHelpers let_it_be(:user) { create(:user) } before do - stub_feature_flags(vue_group_members_list: false) - sign_in(user) end @@ -51,7 +49,6 @@ RSpec.describe 'Groups > Members > Manage groups', :js do end before do - travel_to Time.now.utc.beginning_of_day group_link.update!(additional_link_attrs) shared_group.add_owner(user) @@ -63,8 +60,12 @@ RSpec.describe 'Groups > Members > Manage groups', :js do expect(page).to have_content(shared_with_group.name) - accept_confirm do - find(:css, '#tab-groups li', text: shared_with_group.name).find(:css, 'a.btn-danger').click + page.within(first_row) do + click_button 'Remove group' + end + + page.within('[role="dialog"]') do + click_button('Remove group') end expect(page).not_to have_content(shared_with_group.name) @@ -75,7 +76,7 @@ RSpec.describe 'Groups > Members > Manage groups', :js do page.within(first_row) do click_button('Developer') - click_link('Maintainer') + click_button('Maintainer') wait_for_requests @@ -86,33 +87,30 @@ RSpec.describe 'Groups > Members > Manage groups', :js do it 'updates expiry date' do click_groups_tab - expires_at_field = "member_expires_at_#{shared_with_group.id}" - fill_in "member_expires_at_#{shared_with_group.id}", with: 3.days.from_now.to_date + page.within first_row do + fill_in 'Expiration date', with: 5.days.from_now.to_date + find_field('Expiration date').native.send_keys :enter - find_field(expires_at_field).native.send_keys :enter - wait_for_requests + wait_for_requests - page.within(find('li.group_member')) do - expect(page).to have_content('Expires in 3 days') + expect(page).to have_content(/in \d days/) end end context 'when expiry date is set' do - let(:additional_link_attrs) { { expires_at: 3.days.from_now.to_date } } + let(:additional_link_attrs) { { expires_at: 5.days.from_now.to_date } } it 'clears expiry date' do click_groups_tab - page.within(find('li.group_member')) do - expect(page).to have_content('Expires in 3 days') + page.within first_row do + expect(page).to have_content(/in \d days/) - page.within(find('.js-edit-member-form')) do - find('.js-clear-input').click - end + find('[data-testid="clear-button"]').click wait_for_requests - expect(page).not_to have_content('Expires in') + expect(page).to have_content('No expiration set') end end end @@ -128,6 +126,7 @@ RSpec.describe 'Groups > Members > Manage groups', :js do end def click_groups_tab + expect(page).to have_link 'Groups' click_link "Groups" end end diff --git a/spec/features/groups/members/manage_members_spec.rb b/spec/features/groups/members/manage_members_spec.rb index aedb7c170f8..e6da05c4873 100644 --- a/spec/features/groups/members/manage_members_spec.rb +++ b/spec/features/groups/members/manage_members_spec.rb @@ -4,15 +4,13 @@ require 'spec_helper' RSpec.describe 'Groups > Members > Manage members' do include Select2Helper - include Spec::Support::Helpers::Features::ListRowsHelpers + include Spec::Support::Helpers::Features::MembersHelpers let(:user1) { create(:user, name: 'John Doe') } let(:user2) { create(:user, name: 'Mary Jane') } let(:group) { create(:group) } before do - stub_feature_flags(vue_group_members_list: false) - sign_in(user1) end @@ -24,7 +22,7 @@ RSpec.describe 'Groups > Members > Manage members' do page.within(second_row) do click_button('Developer') - click_link('Owner') + click_button('Owner') expect(page).to have_button('Owner') end @@ -71,11 +69,14 @@ RSpec.describe 'Groups > Members > Manage members' do visit group_group_members_path(group) # Open modal - find(:css, '.project-members-page li', text: user2.name).find(:css, 'button.btn-danger').click - - expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests' + page.within(second_row) do + click_button 'Remove member' + end - click_on('Remove member') + page.within('[role="dialog"]') do + expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests' + click_button('Remove member') + end wait_for_requests @@ -103,16 +104,17 @@ RSpec.describe 'Groups > Members > Manage members' do add_user('test@example.com', 'Reporter') - click_link('Invited') + expect(page).to have_link 'Invited' + click_link 'Invited' - page.within('.content-list.members-list') do + page.within(members_table) do expect(page).to have_content('test@example.com') expect(page).to have_content('Invited') expect(page).to have_button('Reporter') end end - it 'guest can not manage other users' do + it 'guest can not manage other users', :js do group.add_guest(user1) group.add_developer(user2) @@ -126,7 +128,7 @@ RSpec.describe 'Groups > Members > Manage members' do expect(page).not_to have_button 'Developer' # Can not remove user2 - expect(page).not_to have_css('a.btn-danger') + expect(page).not_to have_selector 'button[title="Remove member"]' end end diff --git a/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb index dd708c243a8..de9b32e00aa 100644 --- a/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb +++ b/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb @@ -4,17 +4,13 @@ require 'spec_helper' RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js do include Select2Helper - include ActiveSupport::Testing::TimeHelpers + include Spec::Support::Helpers::Features::MembersHelpers let_it_be(:user1) { create(:user, name: 'John Doe') } let_it_be(:group) { create(:group) } let(:new_member) { create(:user, name: 'Mary Jane') } before do - stub_feature_flags(vue_group_members_list: false) - - travel_to Time.now.utc.beginning_of_day - group.add_owner(user1) sign_in(user1) end @@ -22,17 +18,17 @@ RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js it 'expiration date is displayed in the members list' do visit group_group_members_path(group) - page.within '.invite-users-form' do + page.within invite_users_form do select2(new_member.id, from: '#user_ids', multiple: true) - fill_in 'expires_at', with: 3.days.from_now.to_date + fill_in 'expires_at', with: 5.days.from_now.to_date find_field('expires_at').native.send_keys :enter click_on 'Invite' end - page.within "#group_member_#{group_member_id}" do - expect(page).to have_content('Expires in 3 days') + page.within second_row do + expect(page).to have_content(/in \d days/) end end @@ -40,32 +36,28 @@ RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js group.add_developer(new_member) visit group_group_members_path(group) - page.within "#group_member_#{group_member_id}" do - fill_in 'Expiration date', with: 3.days.from_now.to_date + page.within second_row do + fill_in 'Expiration date', with: 5.days.from_now.to_date find_field('Expiration date').native.send_keys :enter wait_for_requests - expect(page).to have_content('Expires in 3 days') + expect(page).to have_content(/in \d days/) end end it 'clears expiration date' do - create(:group_member, :developer, user: new_member, group: group, expires_at: 3.days.from_now.to_date) + create(:group_member, :developer, user: new_member, group: group, expires_at: 5.days.from_now.to_date) visit group_group_members_path(group) - page.within "#group_member_#{group_member_id}" do - expect(page).to have_content('Expires in 3 days') + page.within second_row do + expect(page).to have_content(/in \d days/) - find('.js-clear-input').click + find('[data-testid="clear-button"]').click wait_for_requests - expect(page).not_to have_content('Expires in') + expect(page).to have_content('No expiration set') end end - - def group_member_id - group.members.find_by(user_id: new_member).id - end end diff --git a/spec/features/groups/members/master_manages_access_requests_spec.rb b/spec/features/groups/members/master_manages_access_requests_spec.rb index 44fd7380b79..71c9b280ebe 100644 --- a/spec/features/groups/members/master_manages_access_requests_spec.rb +++ b/spec/features/groups/members/master_manages_access_requests_spec.rb @@ -3,10 +3,6 @@ require 'spec_helper' RSpec.describe 'Groups > Members > Maintainer manages access requests' do - before do - stub_feature_flags(vue_group_members_list: false) - end - it_behaves_like 'Maintainer manages access requests' do let(:has_tabs) { true } let(:entity) { create(:group, :public) } diff --git a/spec/features/groups/members/search_members_spec.rb b/spec/features/groups/members/search_members_spec.rb index a95b59cece1..0b2d2fd478d 100644 --- a/spec/features/groups/members/search_members_spec.rb +++ b/spec/features/groups/members/search_members_spec.rb @@ -2,7 +2,9 @@ require 'spec_helper' -RSpec.describe 'Search group member' do +RSpec.describe 'Search group member', :js do + include Spec::Support::Helpers::Features::MembersHelpers + let(:user) { create :user } let(:member) { create :user } @@ -14,8 +16,6 @@ RSpec.describe 'Search group member' do end before do - stub_feature_flags(vue_group_members_list: false) - sign_in(user) visit group_group_members_path(guest_group) end @@ -23,11 +23,10 @@ RSpec.describe 'Search group member' do it 'renders member users' do page.within '[data-testid="user-search-form"]' do fill_in 'search', with: member.name - find('.user-search-btn').click + find('[data-testid="user-search-submit"]').click end - group_members_list = find('[data-qa-selector="members_list"]') - expect(group_members_list).to have_content(member.name) - expect(group_members_list).not_to have_content(user.name) + expect(members_table).to have_content(member.name) + expect(members_table).not_to have_content(user.name) end end diff --git a/spec/features/groups/members/sort_members_spec.rb b/spec/features/groups/members/sort_members_spec.rb index d940550b18a..f03cc36df18 100644 --- a/spec/features/groups/members/sort_members_spec.rb +++ b/spec/features/groups/members/sort_members_spec.rb @@ -2,14 +2,16 @@ require 'spec_helper' -RSpec.describe 'Groups > Members > Sort members' do +RSpec.describe 'Groups > Members > Sort members', :js do + include Spec::Support::Helpers::Features::MembersHelpers + let(:owner) { create(:user, name: 'John Doe') } let(:developer) { create(:user, name: 'Mary Jane', last_sign_in_at: 5.days.ago) } let(:group) { create(:group) } - before do - stub_feature_flags(vue_group_members_list: false) + dropdown_toggle_selector = '[data-testid="user-sort-dropdown"] [data-testid="dropdown-toggle"]' + before do create(:group_member, :owner, user: owner, group: group, created_at: 5.days.ago) create(:group_member, :developer, user: developer, group: group, created_at: 3.days.ago) @@ -19,84 +21,76 @@ RSpec.describe 'Groups > Members > Sort members' do it 'sorts alphabetically by default' do visit_members_list(sort: nil) - expect(first_member).to include(owner.name) - expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') + expect(first_row.text).to include(owner.name) + expect(second_row.text).to include(developer.name) + expect(page).to have_css(dropdown_toggle_selector, text: 'Name, ascending') end it 'sorts by access level ascending' do visit_members_list(sort: :access_level_asc) - expect(first_member).to include(developer.name) - expect(second_member).to include(owner.name) - expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending') + expect(first_row.text).to include(developer.name) + expect(second_row.text).to include(owner.name) + expect(page).to have_css(dropdown_toggle_selector, text: 'Access level, ascending') end it 'sorts by access level descending' do visit_members_list(sort: :access_level_desc) - expect(first_member).to include(owner.name) - expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending') + expect(first_row.text).to include(owner.name) + expect(second_row.text).to include(developer.name) + expect(page).to have_css(dropdown_toggle_selector, text: 'Access level, descending') end it 'sorts by last joined' do visit_members_list(sort: :last_joined) - expect(first_member).to include(developer.name) - expect(second_member).to include(owner.name) - expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Last joined') + expect(first_row.text).to include(developer.name) + expect(second_row.text).to include(owner.name) + expect(page).to have_css(dropdown_toggle_selector, text: 'Last joined') end it 'sorts by oldest joined' do visit_members_list(sort: :oldest_joined) - expect(first_member).to include(owner.name) - expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined') + expect(first_row.text).to include(owner.name) + expect(second_row.text).to include(developer.name) + expect(page).to have_css(dropdown_toggle_selector, text: 'Oldest joined') end it 'sorts by name ascending' do visit_members_list(sort: :name_asc) - expect(first_member).to include(owner.name) - expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') + expect(first_row.text).to include(owner.name) + expect(second_row.text).to include(developer.name) + expect(page).to have_css(dropdown_toggle_selector, text: 'Name, ascending') end it 'sorts by name descending' do visit_members_list(sort: :name_desc) - expect(first_member).to include(developer.name) - expect(second_member).to include(owner.name) - expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') + expect(first_row.text).to include(developer.name) + expect(second_row.text).to include(owner.name) + expect(page).to have_css(dropdown_toggle_selector, text: 'Name, descending') end it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do visit_members_list(sort: :recent_sign_in) - expect(first_member).to include(owner.name) - expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') + expect(first_row.text).to include(owner.name) + expect(second_row.text).to include(developer.name) + expect(page).to have_css(dropdown_toggle_selector, text: 'Recent sign in') end it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do visit_members_list(sort: :oldest_sign_in) - expect(first_member).to include(developer.name) - expect(second_member).to include(owner.name) - expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in') + expect(first_row.text).to include(developer.name) + expect(second_row.text).to include(owner.name) + expect(page).to have_css(dropdown_toggle_selector, text: 'Oldest sign in') end def visit_members_list(sort:) visit group_group_members_path(group.to_param, sort: sort) end - - def first_member - page.all('ul.content-list > li').first.text - end - - def second_member - page.all('ul.content-list > li').last.text - end end diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb index 3ae9a2b7555..8d1008b98a6 100644 --- a/spec/features/groups/milestone_spec.rb +++ b/spec/features/groups/milestone_spec.rb @@ -83,6 +83,7 @@ RSpec.describe 'Group milestones' do description: 'Lorem Ipsum is simply dummy text' ) end + let_it_be(:active_project_milestone2) { create(:milestone, project: other_project, state: 'active', title: 'v1.1') } let_it_be(:closed_project_milestone1) { create(:milestone, project: project, state: 'closed', title: 'v2.0') } let_it_be(:closed_project_milestone2) { create(:milestone, project: other_project, state: 'closed', title: 'v2.0') } diff --git a/spec/features/groups/navbar_spec.rb b/spec/features/groups/navbar_spec.rb index e81f2370d10..dec07eb3783 100644 --- a/spec/features/groups/navbar_spec.rb +++ b/spec/features/groups/navbar_spec.rb @@ -50,6 +50,8 @@ RSpec.describe 'Group navbar' do insert_package_nav(_('Kubernetes')) stub_feature_flags(group_iterations: false) + stub_config(dependency_proxy: { enabled: false }) + stub_config(registry: { enabled: false }) stub_group_wikis(false) group.add_maintainer(user) sign_in(user) @@ -73,6 +75,18 @@ RSpec.describe 'Group navbar' do it_behaves_like 'verified navigation bar' end + context 'when dependency proxy is available' do + before do + stub_config(dependency_proxy: { enabled: true }) + + insert_dependency_proxy_nav(_('Dependency Proxy')) + + visit group_path(group) + end + + it_behaves_like 'verified navigation bar' + end + context 'when invite team members is not available' do it 'does not display the js-invite-members-trigger' do visit group_path(group) diff --git a/spec/features/groups/settings/repository_spec.rb b/spec/features/groups/settings/repository_spec.rb index d20303027e5..3c1609a2605 100644 --- a/spec/features/groups/settings/repository_spec.rb +++ b/spec/features/groups/settings/repository_spec.rb @@ -25,4 +25,21 @@ RSpec.describe 'Group Repository settings' do let(:entity_type) { 'group' } end end + + context 'Default initial branch name' do + before do + visit group_settings_repository_path(group) + end + + it 'has the setting section' do + expect(page).to have_css("#js-default-branch-name") + end + + it 'renders the correct setting section content' do + within("#js-default-branch-name") do + expect(page).to have_content("Default initial branch name") + expect(page).to have_content("Set the default name of the initial branch when creating new repositories through the user interface.") + end + end + end end diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb index 304573ecd6e..97732374eb9 100644 --- a/spec/features/groups/show_spec.rb +++ b/spec/features/groups/show_spec.rb @@ -81,8 +81,7 @@ RSpec.describe 'Group show page' do it 'allows creating subgroups' do visit path - expect(page) - .to have_css("li[data-text='New subgroup']", visible: false) + expect(page).to have_link('New subgroup') end end end @@ -102,8 +101,7 @@ RSpec.describe 'Group show page' do path = group_path(relaxed_group) visit path - expect(page) - .to have_css("li[data-text='New subgroup']", visible: false) + expect(page).to have_link('New subgroup') end end @@ -116,9 +114,7 @@ RSpec.describe 'Group show page' do path = group_path(restricted_group) visit path - expect(page) - .not_to have_selector("li[data-text='New subgroup']", - visible: false) + expect(page).not_to have_link('New subgroup') end end end diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 8264ec2eddd..b9fd3a1a5cc 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -294,35 +294,43 @@ RSpec.describe 'Group' do describe 'new subgroup / project button' do let(:group) { create(:group, project_creation_level: Gitlab::Access::NO_ONE_PROJECT_ACCESS, subgroup_creation_level: Gitlab::Access::OWNER_SUBGROUP_ACCESS) } - it 'new subgroup button is displayed without project creation permission' do - visit group_path(group) + context 'when user has subgroup creation permissions but not project creation permissions' do + it 'only displays "New subgroup" button' do + visit group_path(group) - page.within '.group-buttons' do - expect(page).to have_link('New subgroup') + page.within '[data-testid="group-buttons"]' do + expect(page).to have_link('New subgroup') + expect(page).not_to have_link('New project') + end end end - it 'new subgroup button is displayed together with new project button when having project creation permission' do - group.update!(project_creation_level: Gitlab::Access::MAINTAINER_PROJECT_ACCESS) - visit group_path(group) + context 'when user has project creation permissions but not subgroup creation permissions' do + it 'only displays "New project" button' do + group.update!(project_creation_level: Gitlab::Access::MAINTAINER_PROJECT_ACCESS) + user = create(:user) - page.within '.group-buttons' do - expect(page).to have_css("li[data-text='New subgroup']", visible: false) - expect(page).to have_css("li[data-text='New project']", visible: false) + group.add_maintainer(user) + sign_out(:user) + sign_in(user) + + visit group_path(group) + page.within '[data-testid="group-buttons"]' do + expect(page).to have_link('New project') + expect(page).not_to have_link('New subgroup') + end end end - it 'new project button is displayed without subgroup creation permission' do - group.update!(project_creation_level: Gitlab::Access::MAINTAINER_PROJECT_ACCESS) - user = create(:user) - - group.add_maintainer(user) - sign_out(:user) - sign_in(user) + context 'when user has project and subgroup creation permissions' do + it 'displays "New subgroup" and "New project" buttons' do + group.update!(project_creation_level: Gitlab::Access::MAINTAINER_PROJECT_ACCESS) + visit group_path(group) - visit group_path(group) - page.within '.group-buttons' do - expect(page).to have_link('New project') + page.within '[data-testid="group-buttons"]' do + expect(page).to have_link('New subgroup') + expect(page).to have_link('New project') + end end end end diff --git a/spec/features/ide/user_sees_editor_info_spec.rb b/spec/features/ide/user_sees_editor_info_spec.rb new file mode 100644 index 00000000000..3760d6bd435 --- /dev/null +++ b/spec/features/ide/user_sees_editor_info_spec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'IDE user sees editor info', :js do + include WebIdeSpecHelpers + + let_it_be(:project) { create(:project, :public, :repository) } + let_it_be(:user) { project.owner } + + before do + sign_in(user) + + ide_visit(project) + end + + it 'shows line position' do + ide_open_file('README.md') + + within find('.ide-status-bar') do + expect(page).to have_content('1:1') + end + + ide_set_editor_position(4, 10) + + within find('.ide-status-bar') do + expect(page).not_to have_content('1:1') + expect(page).to have_content('4:10') + end + end + + it 'updates after rename' do + ide_open_file('README.md') + ide_set_editor_position(4, 10) + + within find('.ide-status-bar') do + expect(page).to have_content('markdown') + expect(page).to have_content('4:10') + end + + ide_rename_file('README.md', 'READMEZ.txt') + + within find('.ide-status-bar') do + expect(page).to have_content('plaintext') + expect(page).to have_content('1:1') + end + end + + it 'persists position after rename' do + ide_open_file('README.md') + ide_set_editor_position(4, 10) + + ide_open_file('files/js/application.js') + ide_rename_file('README.md', 'READING_RAINBOW.md') + + ide_open_file('READING_RAINBOW.md') + + within find('.ide-status-bar') do + expect(page).to have_content('4:10') + end + end + + it 'persists position' do + ide_open_file('README.md') + ide_set_editor_position(4, 10) + + ide_close_file('README.md') + ide_open_file('README.md') + + within find('.ide-status-bar') do + expect(page).to have_content('markdown') + expect(page).to have_content('4:10') + end + end + + it 'persists viewer' do + ide_open_file('README.md') + click_link('Preview Markdown') + + within find('.md-previewer') do + expect(page).to have_content('testme') + end + + # Switch away from and back to the file + ide_open_file('.gitignore') + ide_open_file('README.md') + + # Preview is still enabled + within find('.md-previewer') do + expect(page).to have_content('testme') + end + end +end diff --git a/spec/features/incidents/user_views_incident_spec.rb b/spec/features/incidents/user_views_incident_spec.rb new file mode 100644 index 00000000000..3595f5c03ec --- /dev/null +++ b/spec/features/incidents/user_views_incident_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "User views incident" do + let_it_be(:project) { create(:project_empty_repo, :public) } + let_it_be(:user) { create(:user) } + let_it_be(:incident) { create(:incident, project: project, description: "# Description header\n\n**Lorem** _ipsum_ dolor sit [amet](https://example.com)", author: user) } + let_it_be(:note) { create(:note, noteable: incident, project: project, author: user) } + + before_all do + project.add_developer(user) + end + + before do + stub_feature_flags(vue_issue_header: false) + + sign_in(user) + + visit(project_issues_incident_path(project, incident)) + end + + it { expect(page).to have_header_with_correct_id_and_link(1, "Description header", "description-header") } + + it_behaves_like 'page meta description', ' Description header Lorem ipsum dolor sit amet' + + it 'shows the merge request and incident actions', :aggregate_failures do + expect(page).to have_link('New incident') + expect(page).to have_button('Create merge request') + expect(page).to have_link('Close incident') + end + + context 'when the project is archived' do + before do + project.update!(archived: true) + visit(project_issues_incident_path(project, incident)) + end + + it 'hides the merge request and incident actions', :aggregate_failures do + expect(page).not_to have_link('New incident') + expect(page).not_to have_button('Create merge request') + expect(page).not_to have_link('Close incident') + end + end + + describe 'user status' do + subject { visit(project_issues_incident_path(project, incident)) } + + context 'when showing status of the author of the incident' do + it_behaves_like 'showing user status' do + let(:user_with_status) { user } + end + end + + context 'when showing status of a user who commented on an incident', :js do + it_behaves_like 'showing user status' do + let(:user_with_status) { user } + end + end + + context 'when status message has an emoji', :js do + let_it_be(:message) { 'My status with an emoji' } + let_it_be(:message_emoji) { 'basketball' } + let_it_be(:status) { create(:user_status, user: user, emoji: 'smirk', message: "#{message} :#{message_emoji}:") } + + it 'correctly renders the emoji' do + wait_for_requests + + tooltip_span = page.first(".user-status-emoji[title^='#{message}']") + tooltip_span.hover + + wait_for_requests + + tooltip = page.find('.tooltip .tooltip-inner') + + page.within(tooltip) do + expect(page).to have_emoji(message_emoji) + end + end + end + end +end diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb index 8ccaf82536a..2ceffa896eb 100644 --- a/spec/features/invites_spec.rb +++ b/spec/features/invites_spec.rb @@ -10,6 +10,7 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do let(:group_invite) { group.group_members.invite.last } before do + stub_application_setting(require_admin_approval_after_user_signup: false) project.add_maintainer(owner) group.add_owner(owner) group.add_developer('user@example.com', owner) @@ -58,6 +59,8 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do end it 'pre-fills the Email field on the sign up box with the invite_email from the invite' do + click_link 'Register now' + expect(find_field('Email').value).to eq(group_invite.invite_email) end @@ -92,6 +95,22 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do before do stub_application_setting(send_user_confirmation_email: send_email_confirmation) visit invite_path(group_invite.raw_invite_token) + click_link 'Register now' + end + + context 'with admin appoval required enabled' do + before do + stub_application_setting(require_admin_approval_after_user_signup: true) + end + + let(:send_email_confirmation) { true } + + it 'does not sign the user in' do + fill_in_sign_up_form(new_user) + + expect(current_path).to eq(new_user_session_path) + expect(page).to have_content('You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your GitLab administrator') + end end context 'email confirmation disabled' do diff --git a/spec/features/issuables/close_reopen_report_toggle_spec.rb b/spec/features/issuables/close_reopen_report_toggle_spec.rb index 6e99cfb3293..867d2ff7aae 100644 --- a/spec/features/issuables/close_reopen_report_toggle_spec.rb +++ b/spec/features/issuables/close_reopen_report_toggle_spec.rb @@ -7,6 +7,10 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do let(:user) { create(:user) } + before do + stub_feature_flags(vue_issue_header: false) + end + shared_examples 'an issuable close/reopen/report toggle' do let(:container) { find('.issuable-close-dropdown') } let(:human_model_name) { issuable.model_name.human.downcase } @@ -95,12 +99,13 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do expect(page).to have_link('New issue') expect(page).not_to have_button('Close issue') expect(page).not_to have_button('Reopen issue') - expect(page).not_to have_link('Edit') + expect(page).not_to have_link(title: 'Edit title and description') end end end context 'on a merge request' do + let(:container) { find('.detail-page-header-actions') } let(:project) { create(:project, :repository) } let(:issuable) { create(:merge_request, source_project: project) } @@ -116,24 +121,47 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do it_behaves_like 'an issuable close/reopen/report toggle' + context 'when the merge request is closed' do + let(:issuable) { create(:merge_request, :closed, source_project: project) } + + it 'shows both the `Edit` and `Reopen` button' do + expect(container).to have_link('Edit') + expect(container).not_to have_button('Report abuse') + expect(container).not_to have_button('Close merge request') + expect(container).to have_link('Reopen merge request') + end + + context 'when the merge request author is the current user' do + let(:issuable) { create(:merge_request, :closed, source_project: project, author: user) } + + it 'shows both the `Edit` and `Reopen` button' do + expect(container).to have_link('Edit') + expect(container).not_to have_link('Report abuse') + expect(container).not_to have_selector('button.dropdown-toggle') + expect(container).not_to have_button('Close merge request') + expect(container).to have_link('Reopen merge request') + end + end + end + context 'when the merge request is merged' do let(:issuable) { create(:merge_request, :merged, source_project: project) } - it 'shows only the `Report abuse` and `Edit` button' do - expect(page).to have_link('Report abuse') - expect(page).to have_link('Edit') - expect(page).not_to have_button('Close merge request') - expect(page).not_to have_button('Reopen merge request') + it 'shows only the `Edit` button' do + expect(container).to have_link(exact_text: 'Edit') + expect(container).not_to have_link('Report abuse') + expect(container).not_to have_button('Close merge request') + expect(container).not_to have_button('Reopen merge request') end context 'when the merge request author is the current user' do let(:issuable) { create(:merge_request, :merged, source_project: project, author: user) } it 'shows only the `Edit` button' do - expect(page).to have_link('Edit') - expect(page).to have_link('Report abuse') - expect(page).not_to have_button('Close merge request') - expect(page).not_to have_button('Reopen merge request') + expect(container).to have_link(exact_text: 'Edit') + expect(container).not_to have_link('Report abuse') + expect(container).not_to have_button('Close merge request') + expect(container).not_to have_button('Reopen merge request') end end end @@ -150,10 +178,10 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do end it 'only shows a `Report abuse` button' do - expect(page).to have_link('Report abuse') - expect(page).not_to have_button('Close merge request') - expect(page).not_to have_button('Reopen merge request') - expect(page).not_to have_link('Edit') + expect(container).to have_link('Report abuse') + expect(container).not_to have_button('Close merge request') + expect(container).not_to have_button('Reopen merge request') + expect(container).not_to have_link(exact_text: 'Edit') end end end diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb index 12682905559..0f0146a26a2 100644 --- a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb +++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb @@ -18,6 +18,10 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j end end + before do + stub_feature_flags(remove_resolve_note: false) + end + describe 'as a user with access to the project' do before do project.add_maintainer(user) diff --git a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb index 55a02dc4255..b449939a70c 100644 --- a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb +++ b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb @@ -14,6 +14,10 @@ RSpec.describe 'Resolve an open thread in a merge request by creating an issue', "a[title=\"#{title}\"][href=\"#{url}\"]" end + before do + stub_feature_flags(remove_resolve_note: false) + end + describe 'As a user with access to the project' do before do project.add_maintainer(user) diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index ff78b9e608f..06f79f94e8d 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -535,6 +535,21 @@ RSpec.describe 'GFM autocomplete', :js do expect(find('.tribute-container ul', visible: true)).to have_text(user_xss.username) end + it 'opens autocomplete menu for Milestone when field starts with text with item escaping HTML characters' do + milestone_xss_title = 'alert milestone <img src=x onerror="alert(\'Hello xss\');" a' + create(:milestone, project: project, title: milestone_xss_title) + + page.within '.timeline-content-form' do + find('#note-body').native.send_keys('%') + end + + wait_for_requests + + expect(page).to have_selector('.tribute-container', visible: true) + + expect(find('.tribute-container ul', visible: true)).to have_text('alert milestone') + end + it 'selects the first item for assignee dropdowns' do page.within '.timeline-content-form' do find('#note-body').native.send_keys('@') @@ -799,6 +814,13 @@ RSpec.describe 'GFM autocomplete', :js do end end + context 'issues' do + let(:object) { issue } + let(:expected_body) { object.to_reference } + + it_behaves_like 'autocomplete suggestions' + end + context 'merge requests' do let(:object) { create(:merge_request, source_project: project) } let(:expected_body) { object.to_reference } @@ -806,6 +828,27 @@ RSpec.describe 'GFM autocomplete', :js do it_behaves_like 'autocomplete suggestions' end + context 'project snippets' do + let!(:object) { create(:project_snippet, project: project, title: 'code snippet') } + let(:expected_body) { object.to_reference } + + it_behaves_like 'autocomplete suggestions' + end + + context 'label' do + let!(:object) { label } + let(:expected_body) { object.title } + + it_behaves_like 'autocomplete suggestions' + end + + context 'milestone' do + let!(:object) { create(:milestone, project: project) } + let(:expected_body) { object.to_reference } + + it_behaves_like 'autocomplete suggestions' + end + context 'when other notes are destroyed' do let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) } diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb index 617eac88973..e225a45481d 100644 --- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb +++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb @@ -250,7 +250,7 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do def test_selection_mark(li_create_branch, li_create_merge_request, button_create_target, button_create_merge_request) page.within(li_create_merge_request) do - expect(page).to have_css('i.fa.fa-check') + expect(page).to have_selector('[data-testid="check-icon"]') expect(button_create_target).to have_text('Create merge request') expect(button_create_merge_request).to have_text('Create merge request') end @@ -258,7 +258,7 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do li_create_branch.click page.within(li_create_branch) do - expect(page).to have_css('i.fa.fa-check') + expect(page).to have_selector('[data-testid="check-icon"]') expect(button_create_target).to have_text('Create branch') expect(button_create_merge_request).to have_text('Create branch') end diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb index de746415205..11b905735de 100644 --- a/spec/features/issues/user_edits_issue_spec.rb +++ b/spec/features/issues/user_edits_issue_spec.rb @@ -138,6 +138,33 @@ RSpec.describe "Issues > User edits issue", :js do expect(page).not_to have_text('verisimilitude') end end + + it 'can remove label without removing label added via quick action', :aggregate_failures do + # Add `syzygy` label with a quick action + note = find('#note-body') + page.within '.timeline-content-form' do + note.native.send_keys('/label ~syzygy') + end + click_button 'Comment' + + wait_for_requests + + page.within '.block.labels' do + # Remove `verisimilitude` label + within '.gl-label' do + click_button + end + + wait_for_requests + + expect(page).to have_text('syzygy') + expect(page).not_to have_text('verisimilitude') + end + + expect(page).to have_text('removed verisimilitude label') + expect(page).not_to have_text('removed syzygy verisimilitude labels') + expect(issue.reload.labels.map(&:title)).to contain_exactly('syzygy') + end end describe 'update assignee' do diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb index 7db72f2cd05..fec603e466a 100644 --- a/spec/features/issues/user_interacts_with_awards_spec.rb +++ b/spec/features/issues/user_interacts_with_awards_spec.rb @@ -68,7 +68,7 @@ RSpec.describe 'User interacts with awards' do page.within('.awards') do expect(page).to have_selector('.js-emoji-btn') expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1') - expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']") + expect(page).to have_css(".js-emoji-btn.active[title='You']") expect do page.find('.js-emoji-btn.active').click @@ -294,7 +294,7 @@ RSpec.describe 'User interacts with awards' do end end - it 'toggles the smiley emoji on a note', :js do + it 'toggles the smiley emoji on a note', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/267525' do toggle_smiley_emoji(true) within('.note-body') do diff --git a/spec/features/issues/user_sees_live_update_spec.rb b/spec/features/issues/user_sees_live_update_spec.rb index d27cdb774a5..79c6978cbc0 100644 --- a/spec/features/issues/user_sees_live_update_spec.rb +++ b/spec/features/issues/user_sees_live_update_spec.rb @@ -26,7 +26,7 @@ RSpec.describe 'Issues > User sees live update', :js do end describe 'confidential issue#show' do - it 'shows confidential sibebar information as confidential and can be turned off' do + it 'shows confidential sibebar information as confidential and can be turned off', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/254644' do issue = create(:issue, :confidential, project: project) visit project_issue_path(project, issue) diff --git a/spec/features/issues/user_views_issue_spec.rb b/spec/features/issues/user_views_issue_spec.rb index 9b1c8be1513..4128f3478bb 100644 --- a/spec/features/issues/user_views_issue_spec.rb +++ b/spec/features/issues/user_views_issue_spec.rb @@ -13,6 +13,8 @@ RSpec.describe "User views issue" do end before do + stub_feature_flags(vue_issue_header: false) + sign_in(user) visit(project_issue_path(project, issue)) diff --git a/spec/features/merge_request/user_comments_on_diff_spec.rb b/spec/features/merge_request/user_comments_on_diff_spec.rb index ad1ad067935..c452408cff2 100644 --- a/spec/features/merge_request/user_comments_on_diff_spec.rb +++ b/spec/features/merge_request/user_comments_on_diff_spec.rb @@ -136,12 +136,6 @@ RSpec.describe 'User comments on a diff', :js do add_comment('-13', '+15') end - it 'allows comments to start above hidden lines and end below' do - # click +28, select 21 add and verify comment - click_diff_line(find('div[data-path="files/ruby/popen.rb"] .new_line a[data-linenumber="28"]').find(:xpath, '../..'), 'right') - add_comment('21', '+28') - end - it 'allows comments on previously hidden lines at the top of a file' do # Click -9, expand up, select 1 add and verify comment page.within('[data-path="files/ruby/popen.rb"]') do diff --git a/spec/features/merge_request/user_comments_on_merge_request_spec.rb b/spec/features/merge_request/user_comments_on_merge_request_spec.rb index 73f2b1a25ce..43096f8e7f9 100644 --- a/spec/features/merge_request/user_comments_on_merge_request_spec.rb +++ b/spec/features/merge_request/user_comments_on_merge_request_spec.rb @@ -30,6 +30,27 @@ RSpec.describe 'User comments on a merge request', :js do end end + it 'replys to a new comment' do + page.within('.js-main-target-form') do + fill_in('note[note]', with: 'comment 1') + click_button('Comment') + end + + wait_for_requests + + page.within('.note') do + click_button('Reply to comment') + + fill_in('note[note]', with: 'comment 2') + click_button('Add comment now') + end + + wait_for_requests + + # Test that the discussion doesn't get auto-resolved + expect(page).to have_button('Resolve thread') + end + it 'loads new comment' do # Add new comment in background in order to check # if it's going to be loaded automatically for current user. diff --git a/spec/features/merge_request/user_expands_diff_spec.rb b/spec/features/merge_request/user_expands_diff_spec.rb index 0cdc87de761..09c5897f102 100644 --- a/spec/features/merge_request/user_expands_diff_spec.rb +++ b/spec/features/merge_request/user_expands_diff_spec.rb @@ -8,6 +8,8 @@ RSpec.describe 'User expands diff', :js do before do stub_feature_flags(increased_diff_limits: false) + allow(Gitlab::CurrentSettings).to receive(:diff_max_patch_bytes).and_return(100.kilobytes) + visit(diffs_project_merge_request_path(project, merge_request)) wait_for_requests @@ -15,11 +17,11 @@ RSpec.describe 'User expands diff', :js do it 'allows user to expand diff' do page.within find('[id="19763941ab80e8c09871c0a425f0560d9053bcb3"]') do - click_link 'Click to expand it.' + find('[data-testid="expand-button"]').click wait_for_requests - expect(page).not_to have_content('Click to expand it.') + expect(page).not_to have_content('Expand file') expect(page).to have_selector('.code') end end diff --git a/spec/features/merge_request/user_merges_immediately_spec.rb b/spec/features/merge_request/user_merges_immediately_spec.rb index 0fb081ec507..64a357de1f7 100644 --- a/spec/features/merge_request/user_merges_immediately_spec.rb +++ b/spec/features/merge_request/user_merges_immediately_spec.rb @@ -34,7 +34,7 @@ RSpec.describe 'Merge requests > User merges immediately', :js do find('.dropdown-toggle').click Sidekiq::Testing.fake! do - click_link 'Merge immediately' + click_button 'Merge immediately' expect(find('.accept-merge-request.btn-info')).to have_content('Merge in progress') diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb index 444d5371e7a..5e99383e4a1 100644 --- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb +++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb @@ -93,19 +93,6 @@ RSpec.describe 'Merge request > User merges when pipeline succeeds', :js do it_behaves_like 'Merge when pipeline succeeds activator' end end - - describe 'enabling Merge when pipeline succeeds via dropdown' do - it 'activates the Merge when pipeline succeeds feature' do - wait_for_requests - - find('.js-merge-moment').click - click_link 'Merge when pipeline succeeds' - - expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds" - expect(page).to have_content "The source branch will not be deleted" - expect(page).to have_link "Cancel automatic merge" - end - end end context 'when merge when pipeline succeeds is enabled' do diff --git a/spec/features/merge_request/user_resolves_conflicts_spec.rb b/spec/features/merge_request/user_resolves_conflicts_spec.rb index f96408fb10b..06405232462 100644 --- a/spec/features/merge_request/user_resolves_conflicts_spec.rb +++ b/spec/features/merge_request/user_resolves_conflicts_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Merge request > User resolves conflicts', :js do + include Spec::Support::Helpers::Features::EditorLiteSpecHelpers + let(:project) { create(:project, :repository) } let(:user) { project.creator } @@ -64,15 +66,13 @@ RSpec.describe 'Merge request > User resolves conflicts', :js do within find('.files-wrapper .diff-file', text: 'files/ruby/popen.rb') do click_button 'Edit inline' wait_for_requests - find('.files-wrapper .diff-file pre') - execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("One morning");') + editor_set_value("One morning") end within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do click_button 'Edit inline' wait_for_requests - find('.files-wrapper .diff-file pre') - execute_script('ace.edit($(".files-wrapper .diff-file pre")[1]).setValue("Gregor Samsa woke from troubled dreams");') + editor_set_value("Gregor Samsa woke from troubled dreams") end find_button('Commit to source branch').send_keys(:return) diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb index cd06886169d..00f0c88497b 100644 --- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb +++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb @@ -15,6 +15,10 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do diff_refs: merge_request.diff_refs) end + before do + stub_feature_flags(remove_resolve_note: false) + end + context 'no threads' do before do project.add_maintainer(user) diff --git a/spec/features/merge_request/user_resolves_wip_mr_spec.rb b/spec/features/merge_request/user_resolves_wip_mr_spec.rb index b67167252e1..93b14279a06 100644 --- a/spec/features/merge_request/user_resolves_wip_mr_spec.rb +++ b/spec/features/merge_request/user_resolves_wip_mr_spec.rb @@ -33,7 +33,7 @@ RSpec.describe 'Merge request > User resolves Work in Progress', :js do it 'retains merge request data after clicking Resolve WIP status' do expect(page.find('.ci-widget-content')).to have_content("Pipeline ##{pipeline.id}") - expect(page).to have_content "This merge request is still a work in progress." + expect(page).to have_content "This merge request is still a draft." page.within('.mr-state-widget') do click_button('Mark as ready') @@ -45,7 +45,7 @@ RSpec.describe 'Merge request > User resolves Work in Progress', :js do # merge request widget refreshes, which masks missing elements # that should already be present. expect(page.find('.ci-widget-content', wait: 0)).to have_content("Pipeline ##{pipeline.id}") - expect(page).not_to have_content('This merge request is still a work in progress.') + expect(page).not_to have_content('This merge request is still a draft.') end end end diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb index 93fea44707c..0e8012f161f 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -558,8 +558,9 @@ RSpec.describe 'Merge request > User sees merge widget', :js do end before do - allow_any_instance_of(TestSuiteComparerEntity) - .to receive(:max_tests).and_return(2) + stub_const("Gitlab::Ci::Reports::TestSuiteComparer::DEFAULT_MAX_TESTS", 2) + stub_const("Gitlab::Ci::Reports::TestSuiteComparer::DEFAULT_MIN_TESTS", 1) + allow_any_instance_of(MergeRequest) .to receive(:has_test_reports?).and_return(true) allow_any_instance_of(MergeRequest) diff --git a/spec/features/merge_request/user_sees_suggest_pipeline_spec.rb b/spec/features/merge_request/user_sees_suggest_pipeline_spec.rb index 93807512d9c..4bb6c3265a4 100644 --- a/spec/features/merge_request/user_sees_suggest_pipeline_spec.rb +++ b/spec/features/merge_request/user_sees_suggest_pipeline_spec.rb @@ -9,8 +9,6 @@ RSpec.describe 'Merge request > User sees suggest pipeline', :js do before do stub_application_setting(auto_devops_enabled: false) - stub_experiment(suggest_pipeline: true) - stub_experiment_for_user(suggest_pipeline: true) project.add_maintainer(user) sign_in(user) visit project_merge_request_path(project, merge_request) @@ -32,4 +30,40 @@ RSpec.describe 'Merge request > User sees suggest pipeline', :js do expect(page).not_to have_content('Are you adding technical debt or code vulnerabilities?') end + + it 'runs tour from start to finish ensuring all nudges are executed' do + # nudge 1 + expect(page).to have_content('Are you adding technical debt or code vulnerabilities?') + + page.within '.mr-pipeline-suggest' do + find('[data-testid="ok"]').click + end + + wait_for_requests + + # nudge 2 + expect(page).to have_content('Choose Code Quality to add a pipeline that tests the quality of your code.') + + find('.js-gitlab-ci-yml-selector').click + + wait_for_requests + + within '.gitlab-ci-yml-selector' do + find('.dropdown-input-field').set('Jekyll') + find('.dropdown-content li', text: 'Jekyll').click + end + + wait_for_requests + + expect(page).not_to have_content('Choose Code Quality to add a pipeline that tests the quality of your code.') + # nudge 3 + expect(page).to have_content('The template is ready!') + + find('#commit-changes').click + + wait_for_requests + + # nudge 4 + expect(page).to have_content("That's it, well done!") + end end diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb index 9268190c7e0..1e1888cd826 100644 --- a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb +++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb @@ -304,7 +304,7 @@ RSpec.describe 'User comments on a diff', :js do wait_for_requests end - it 'suggestion is presented' do + it 'suggestion is presented', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/268240' do page.within('.diff-discussions') do expect(page).to have_button('Apply suggestion') expect(page).to have_content('Suggested change') diff --git a/spec/features/merge_request/user_views_diffs_spec.rb b/spec/features/merge_request/user_views_diffs_spec.rb index 928755bf5de..e1865fe2e14 100644 --- a/spec/features/merge_request/user_views_diffs_spec.rb +++ b/spec/features/merge_request/user_views_diffs_spec.rb @@ -61,7 +61,7 @@ RSpec.describe 'User views diffs', :js do end it 'expands all diffs' do - first('.js-file-title').click + first('.diff-toggle-caret').click expect(page).to have_button('Expand all') diff --git a/spec/features/merge_requests/user_exports_as_csv_spec.rb b/spec/features/merge_requests/user_exports_as_csv_spec.rb new file mode 100644 index 00000000000..a86ff9d7335 --- /dev/null +++ b/spec/features/merge_requests/user_exports_as_csv_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Merge Requests > Exports as CSV', :js do + let!(:project) { create(:project, :public, :repository) } + let!(:user) { project.creator } + let!(:open_mr) { create(:merge_request, title: 'Bugfix1', source_project: project, target_project: project, source_branch: 'bugfix1') } + + before do + sign_in(user) + visit(project_merge_requests_path(project)) + end + + subject { page.find('.nav-controls') } + + it { is_expected.to have_button('Export as CSV') } + + context 'button is clicked' do + before do + click_button('Export as CSV') + end + + it 'shows a success message' do + click_link('Export merge requests') + + expect(page).to have_content 'Your CSV export has started.' + expect(page).to have_content "It will be emailed to #{user.email} when complete" + end + end +end diff --git a/spec/features/merge_requests/user_filters_by_draft_spec.rb b/spec/features/merge_requests/user_filters_by_draft_spec.rb new file mode 100644 index 00000000000..de070805d96 --- /dev/null +++ b/spec/features/merge_requests/user_filters_by_draft_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Merge Requests > User filters by draft', :js do + include FilteredSearchHelpers + + let(:project) { create(:project, :public, :repository) } + let(:user) { project.creator } + + before do + create(:merge_request, title: 'Draft: Bugfix', source_project: project, target_project: project, source_branch: 'bugfix2') + + sign_in(user) + visit project_merge_requests_path(project) + end + + it 'filters results' do + input_filtered_search_keys('draft:=yes') + + expect(page).to have_content('Draft: Bugfix') + end + + it 'does not allow filtering by is not equal' do + find('#filtered-search-merge_requests').click + + click_button 'Draft' + + expect(page).not_to have_content('!=') + end +end diff --git a/spec/features/merge_requests/user_filters_by_target_branch_spec.rb b/spec/features/merge_requests/user_filters_by_target_branch_spec.rb index 540d87eb969..1d9c80238f5 100644 --- a/spec/features/merge_requests/user_filters_by_target_branch_spec.rb +++ b/spec/features/merge_requests/user_filters_by_target_branch_spec.rb @@ -44,4 +44,14 @@ RSpec.describe 'Merge Requests > User filters by target branch', :js do expect(page).not_to have_content mr2.title end end + + context 'filtering by target-branch:!=master' do + it 'applies the filter' do + input_filtered_search('target-branch:!=master') + + expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) + expect(page).not_to have_content mr1.title + expect(page).to have_content mr2.title + end + end end diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb index fefa2916c30..dce76e4df6d 100644 --- a/spec/features/milestone_spec.rb +++ b/spec/features/milestone_spec.rb @@ -76,7 +76,7 @@ RSpec.describe 'Milestone' do wait_for_requests - page.within('.time-tracking-no-tracking-pane') do + page.within('[data-testid="noTrackingPane"]') do expect(page).to have_content 'No estimate or time spent' end end @@ -94,7 +94,7 @@ RSpec.describe 'Milestone' do wait_for_requests - page.within('.time-tracking-spend-only-pane') do + page.within('[data-testid="spentOnlyPane"]') do expect(page).to have_content 'Spent: 3h' end end diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb index 4326700bab1..84ea9495f08 100644 --- a/spec/features/profile_spec.rb +++ b/spec/features/profile_spec.rb @@ -12,6 +12,9 @@ RSpec.describe 'Profile account page', :js do describe 'when I delete my account' do before do visit profile_account_path + + # Scroll page to the bottom to make Delete account button visible + execute_script('window.scrollTo(0, document.body.scrollHeight)') end it { expect(page).to have_content('Delete account') } @@ -101,10 +104,10 @@ RSpec.describe 'Profile account page', :js do it 'changes my username' do fill_in 'username-change-input', with: 'new-username' - page.find('[data-target="#username-change-confirmation-modal"]').click + page.find('[data-testid="username-change-confirmation-modal"]').click page.within('.modal') do - find('.js-modal-primary-action').click + find('.js-modal-action-primary').click end expect(page).to have_content('new-username') diff --git a/spec/features/profiles/account_spec.rb b/spec/features/profiles/account_spec.rb index e8caa2159a4..62d8a96c1b2 100644 --- a/spec/features/profiles/account_spec.rb +++ b/spec/features/profiles/account_spec.rb @@ -33,7 +33,7 @@ RSpec.describe 'Profile > Account', :js do end it 'allows the user to disconnect when there is an existing identity' do - expect(page).to have_link('Disconnect Twitter', href: '/profile/account/unlink?provider=twitter') + expect(page).to have_link('Disconnect Twitter', href: '/-/profile/account/unlink?provider=twitter') end it 'shows active for a provider that is not allowed to unlink' do @@ -128,10 +128,10 @@ def update_username(new_username) fill_in 'username-change-input', with: new_username - page.find('[data-target="#username-change-confirmation-modal"]').click + page.find('[data-testid="username-change-confirmation-modal"]').click page.within('.modal') do - find('.js-modal-primary-action').click + find('.js-modal-action-primary').click end wait_for_requests diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb index 4438831fb76..de5a594aca6 100644 --- a/spec/features/profiles/personal_access_tokens_spec.rb +++ b/spec/features/profiles/personal_access_tokens_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe 'Profile > Personal Access Tokens', :js do let(:user) { create(:user) } + let(:pat_create_service) { double('PersonalAccessTokens::CreateService', execute: ServiceResponse.error(message: 'error', payload: { personal_access_token: PersonalAccessToken.new })) } def active_personal_access_tokens find(".table.active-tokens") @@ -18,7 +19,7 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do end def disallow_personal_access_token_saves! - allow_any_instance_of(PersonalAccessToken).to receive(:save).and_return(false) + allow(PersonalAccessTokens::CreateService).to receive(:new).and_return(pat_create_service) errors = ActiveModel::Errors.new(PersonalAccessToken.new).tap { |e| e.add(:name, "cannot be nil") } allow_any_instance_of(PersonalAccessToken).to receive(:errors).and_return(errors) @@ -100,7 +101,10 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do context "when revocation fails" do it "displays an error message" do visit profile_personal_access_tokens_path - allow_any_instance_of(PersonalAccessTokens::RevokeService).to receive(:revocation_permitted?).and_return(false) + + allow_next_instance_of(PersonalAccessTokens::RevokeService) do |instance| + allow(instance).to receive(:revocation_permitted?).and_return(false) + end accept_confirm { click_on "Revoke" } expect(active_personal_access_tokens).to have_text(personal_access_token.name) diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index 697fccaca34..d0340dfc880 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -20,6 +20,10 @@ RSpec.describe 'User edit profile' do wait_for_requests end + def toggle_busy_status + find('[data-testid="user-availability-checkbox"]').set(true) + end + it 'changes user profile' do fill_in 'user_skype', with: 'testskype' fill_in 'user_linkedin', with: 'testlinkedin' @@ -180,20 +184,51 @@ RSpec.describe 'User edit profile' do expect(page).to have_emoji('speech_balloon') end end + + it 'sets the users status to busy' do + busy_status = find('[data-testid="user-availability-checkbox"]') + + expect(busy_status.checked?).to eq(false) + + toggle_busy_status + submit_settings + visit profile_path + + expect(busy_status.checked?).to eq(true) + end + + context 'with set_user_availability_status feature flag disabled' do + before do + stub_feature_flags(set_user_availability_status: false) + visit root_path(user) + end + + it 'does not display the availability checkbox' do + expect(page).not_to have_css('[data-testid="user-availability-checkbox"]') + end + end end context 'user menu' do let(:issue) { create(:issue, project: project)} let(:project) { create(:project) } - def open_user_status_modal + def open_modal(button_text) find('.header-user-dropdown-toggle').click page.within ".header-user" do - click_button 'Set status' + click_button button_text end end + def open_user_status_modal + open_modal 'Set status' + end + + def open_edit_status_modal + open_modal 'Edit status' + end + def set_user_status_in_modal page.within "#set-user-status-modal" do click_button 'Set status' @@ -246,6 +281,19 @@ RSpec.describe 'User edit profile' do end end + it 'sets the users status to busy' do + open_user_status_modal + busy_status = find('[data-testid="user-availability-checkbox"]') + + expect(busy_status.checked?).to eq(false) + + toggle_busy_status + set_user_status_in_modal + open_edit_status_modal + + expect(busy_status.checked?).to eq(true) + end + it 'opens the emoji modal again after closing it' do open_user_status_modal select_emoji('biohazard', true) @@ -307,11 +355,7 @@ RSpec.describe 'User edit profile' do expect(page).to have_content user_status.message end - find('.header-user-dropdown-toggle').click - - page.within ".header-user" do - click_button 'Edit status' - end + open_edit_status_modal find('.js-clear-user-status-button').click set_user_status_in_modal @@ -333,11 +377,7 @@ RSpec.describe 'User edit profile' do expect(page).to have_content user_status.message end - find('.header-user-dropdown-toggle').click - - page.within ".header-user" do - click_button 'Edit status' - end + open_edit_status_modal page.within "#set-user-status-modal" do click_button 'Remove status' @@ -357,6 +397,19 @@ RSpec.describe 'User edit profile' do expect(page).to have_emoji('speech_balloon') end end + + context 'with set_user_availability_status feature flag disabled' do + before do + stub_feature_flags(set_user_availability_status: false) + visit root_path(user) + end + + it 'does not display the availability checkbox' do + open_user_status_modal + + expect(page).not_to have_css('[data-testid="user-availability-checkbox"]') + end + end end context 'User time preferences', :js do diff --git a/spec/features/project_group_variables_spec.rb b/spec/features/project_group_variables_spec.rb index e964a7def14..d8eba20ac18 100644 --- a/spec/features/project_group_variables_spec.rb +++ b/spec/features/project_group_variables_spec.rb @@ -24,7 +24,6 @@ RSpec.describe 'Project group variables', :js do sign_in(user) project.add_maintainer(user) group.add_owner(user) - stub_feature_flags(new_variables_ui: false) end it 'project in group shows inherited vars from ancestor group' do @@ -53,9 +52,13 @@ RSpec.describe 'Project group variables', :js do it 'project origin keys link to ancestor groups ci_cd settings' do visit project_path + find('.group-origin-link').click - page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do - expect(find('.js-ci-variable-input-key').value).to eq(key1) + + wait_for_requests + + page.within('.ci-variable-table') do + expect(find('.js-ci-variable-row:nth-child(1) [data-label="Key"]').text).to eq(key1) end end end diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb index c67bcbf919b..a7f94f38d85 100644 --- a/spec/features/project_variables_spec.rb +++ b/spec/features/project_variables_spec.rb @@ -12,32 +12,29 @@ RSpec.describe 'Project variables', :js do sign_in(user) project.add_maintainer(user) project.variables << variable - stub_feature_flags(new_variables_ui: false) visit page_path end it_behaves_like 'variable list' - it 'adds new variable with a special environment scope' do - page.within('.js-ci-variable-list-section .js-row:last-child') do - find('.js-ci-variable-input-key').set('somekey') - find('.js-ci-variable-input-value').set('somevalue') + it 'adds a new variable with an environment scope' do + click_button('Add Variable') - find('.js-variable-environment-toggle').click - find('.js-variable-environment-dropdown-wrapper .dropdown-input-field').set('review/*') - find('.js-variable-environment-dropdown-wrapper .js-dropdown-create-new-item').click + page.within('#add-ci-variable') do + find('[data-qa-selector="ci_variable_key_field"] input').set('akey') + find('#ci-variable-value').set('akey_value') + find('[data-testid="environment-scope"]').click + find_button('clear').click + find('[data-testid="ci-environment-search"]').set('review/*') + find('[data-testid="create-wildcard-button"]').click - expect(find('input[name="variables[variables_attributes][][environment_scope]"]', visible: false).value).to eq('review/*') + click_button('Add variable') end - click_button('Save variables') wait_for_requests - visit page_path - - page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do - expect(find('.js-ci-variable-input-key').value).to eq('somekey') - expect(page).to have_content('review/*') + page.within('.ci-variable-table') do + expect(find('.js-ci-variable-row:first-child [data-label="Environments"]').text).to eq('review/*') end end end diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb index c30c8dda852..3f1c10b3688 100644 --- a/spec/features/projects/blobs/edit_spec.rb +++ b/spec/features/projects/blobs/edit_spec.rb @@ -179,12 +179,14 @@ RSpec.describe 'Editing file blob', :js do end context 'with protected branch' do - before do - visit project_edit_blob_path(project, tree_join(protected_branch, file_path)) - end - it 'shows blob editor with patch branch' do - expect(find('.js-branch-name').value).to eq('patch-1') + freeze_time do + visit project_edit_blob_path(project, tree_join(protected_branch, file_path)) + + epoch = Time.now.strftime('%s%L').last(5) + + expect(find('.js-branch-name').value).to eq "#{user.username}-protected-branch-patch-#{epoch}" + end end end end diff --git a/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb b/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb index fda2992af8d..6b9fd41059d 100644 --- a/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb +++ b/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb @@ -23,8 +23,6 @@ RSpec.describe 'User creates new blob', :js do ide_commit - click_button('Commit') - expect(page).to have_content('All changes are committed') expect(project.repository.blob_at('master', 'dummy-file').data).to eql("Hello world\n") end diff --git a/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb b/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb index 023e00a3e02..3069405ba63 100644 --- a/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb +++ b/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb @@ -10,7 +10,6 @@ RSpec.describe 'User follows pipeline suggest nudge spec when feature is enabled describe 'viewing the new blob page' do before do - stub_experiment_for_user(suggest_pipeline: true) sign_in(user) end diff --git a/spec/features/projects/ci/editor_spec.rb b/spec/features/projects/ci/editor_spec.rb new file mode 100644 index 00000000000..7012cc6edaa --- /dev/null +++ b/spec/features/projects/ci/editor_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Pipeline Editor', :js do + include Spec::Support::Helpers::Features::EditorLiteSpecHelpers + + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + + before do + sign_in(user) + project.add_developer(user) + + visit project_ci_pipeline_editor_path(project) + end + + it 'user sees the Pipeline Editor page' do + expect(page).to have_content('Pipeline Editor') + end +end diff --git a/spec/features/projects/ci/lint_spec.rb b/spec/features/projects/ci/lint_spec.rb index eb2efb4357d..466c7ba215e 100644 --- a/spec/features/projects/ci/lint_spec.rb +++ b/spec/features/projects/ci/lint_spec.rb @@ -11,7 +11,6 @@ RSpec.describe 'CI Lint', :js do let(:content_selector) { '.content .view-lines' } before do - stub_feature_flags(ci_lint_vue: false) project.add_developer(user) sign_in(user) @@ -26,7 +25,6 @@ RSpec.describe 'CI Lint', :js do describe 'YAML parsing' do shared_examples 'validates the YAML' do before do - stub_feature_flags(ci_lint_vue: false) click_on 'Validate' end @@ -68,14 +66,6 @@ RSpec.describe 'CI Lint', :js do it_behaves_like 'validates the YAML' end - - describe 'YAML revalidate' do - let(:yaml_content) { 'my yaml content' } - - it 'loads previous YAML content after validation' do - expect(page).to have_field('content', with: 'my yaml content', visible: false, type: 'textarea') - end - end end describe 'YAML clearing' do @@ -89,7 +79,7 @@ RSpec.describe 'CI Lint', :js do end it 'YAML content is cleared' do - expect(page).to have_field('content', with: '', visible: false, type: 'textarea') + expect(page).to have_field(class: 'inputarea', with: '', visible: false, type: 'textarea') end end end diff --git a/spec/features/projects/container_registry_spec.rb b/spec/features/projects/container_registry_spec.rb index 7514a26f020..45bf35a6aab 100644 --- a/spec/features/projects/container_registry_spec.rb +++ b/spec/features/projects/container_registry_spec.rb @@ -10,6 +10,10 @@ RSpec.describe 'Container Registry', :js do create(:container_repository, name: 'my/image') end + let(:nameless_container_repository) do + create(:container_repository, name: '') + end + before do sign_in(user) project.add_developer(user) @@ -96,6 +100,20 @@ RSpec.describe 'Container Registry', :js do end end + describe 'image repo details when image has no name' do + before do + stub_container_registry_tags(tags: %w[latest], with_manifest: true) + project.container_repositories << nameless_container_repository + visit_container_registry + end + + it 'renders correctly' do + find('a[data-testid="details-link"]').click + + expect(page).to have_content 'latest' + end + end + context 'when there are more than 10 images' do before do create_list(:container_repository, 12, project: project) diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb index d28e31c08dc..42f8daf9d5e 100644 --- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb +++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb @@ -27,9 +27,7 @@ RSpec.describe 'Projects > Files > Project owner sees a link to create a license ide_commit - click_button('Commit') - - expect(current_path).to eq("/-/ide/project/#{project.full_path}/tree/master/-/") + expect(current_path).to eq("/-/ide/project/#{project.full_path}/tree/master/-/LICENSE/") expect(page).to have_content('All changes are committed') diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb index a6126fbcb33..4e9e129042c 100644 --- a/spec/features/projects/files/user_browses_files_spec.rb +++ b/spec/features/projects/files/user_browses_files_spec.rb @@ -87,26 +87,22 @@ RSpec.describe "User browses files" do end it "shows correct files and links" do - # rubocop:disable Lint/Void - # Test the full URLs of links instead of relative paths by `have_link(text: "...", href: "...")`. - find("a", text: /^empty$/)["href"] == project_tree_url(project, "markdown") - find("a", text: /^#id$/)["href"] == project_tree_url(project, "markdown", anchor: "#id") - find("a", text: %r{^/#id$})["href"] == project_tree_url(project, "markdown", anchor: "#id") - find("a", text: /^README.md#id$/)["href"] == project_blob_url(project, "markdown/README.md", anchor: "#id") - find("a", text: %r{^d/README.md#id$})["href"] == project_blob_url(project, "d/markdown/README.md", anchor: "#id") - # rubocop:enable Lint/Void - expect(current_path).to eq(project_tree_path(project, "markdown")) expect(page).to have_content("README.md") - .and have_content("CHANGELOG") - .and have_content("Welcome to GitLab GitLab is a free project and repository management application") - .and have_link("GitLab API doc") - .and have_link("GitLab API website") - .and have_link("Rake tasks") - .and have_link("backup and restore procedure") - .and have_link("GitLab API doc directory") - .and have_link("Maintenance") - .and have_header_with_correct_id_and_link(2, "Application details", "application-details") + .and have_content("CHANGELOG") + .and have_content("Welcome to GitLab GitLab is a free project and repository management application") + .and have_link("GitLab API doc") + .and have_link("GitLab API website") + .and have_link("Rake tasks") + .and have_link("backup and restore procedure") + .and have_link("GitLab API doc directory") + .and have_link("Maintenance") + .and have_header_with_correct_id_and_link(2, "Application details", "application-details") + .and have_link("empty", href: "") + .and have_link("#id", href: "#id") + .and have_link("/#id", href: project_blob_path(project, "markdown/README.md", anchor: "id")) + .and have_link("README.md#id", href: project_blob_path(project, "markdown/README.md", anchor: "id")) + .and have_link("d/README.md#id", href: project_blob_path(project, "markdown/db/README.md", anchor: "id")) end it "shows correct content of file" do @@ -114,10 +110,10 @@ RSpec.describe "User browses files" do expect(current_path).to eq(project_blob_path(project, "markdown/doc/api/README.md")) expect(page).to have_content("All API requests require authentication") - .and have_content("Contents") - .and have_link("Users") - .and have_link("Rake tasks") - .and have_header_with_correct_id_and_link(1, "GitLab API", "gitlab-api") + .and have_content("Contents") + .and have_link("Users") + .and have_link("Rake tasks") + .and have_header_with_correct_id_and_link(1, "GitLab API", "gitlab-api") click_link("Users") @@ -148,16 +144,13 @@ RSpec.describe "User browses files" do click_link("d") end - # rubocop:disable Lint/Void - # Test the full URLs of links instead of relative paths by `have_link(text: "...", href: "...")`. - find("a", text: "..")["href"] == project_tree_url(project, "markdown/d") - # rubocop:enable Lint/Void + expect(page).to have_link("..", href: project_tree_path(project, "markdown/")) page.within(".tree-table") do click_link("README.md") end - # Test the full URLs of links instead of relative paths by `have_link(text: "...", href: "...")`. - find("a", text: /^empty$/)["href"] == project_blob_url(project, "markdown/d/README.md") + + expect(page).to have_link("empty", href: "") end it "shows correct content of directory" do diff --git a/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb b/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb index de1fcc9d787..b5d5527bbfe 100644 --- a/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb +++ b/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb @@ -51,7 +51,7 @@ RSpec.describe 'User uploads new design', :js do end def upload_design(fixture, count:) - attach_file(:design_file, fixture, match: :first, make_visible: true) + attach_file(:upload_file, fixture, match: :first, make_visible: true) wait_for('designs uploaded') do issue.reload.designs.count == count diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 404c3e93586..e19337e1ff5 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -1013,7 +1013,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do before do job.run! visit project_job_path(project, job) - find('.js-cancel-job').click + find('[data-testid="cancel-button"]').click end it 'loads the page and shows all needed controls' do @@ -1030,7 +1030,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do visit project_job_path(project, job) wait_for_requests - find('.js-retry-button').click + find('[data-testid="retry-button"]').click end it 'shows the right status and buttons' do @@ -1057,6 +1057,31 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do end end end + + context "Job that failed because of a forward deployment failure" do + let(:job) { create(:ci_build, :forward_deployment_failure, pipeline: pipeline) } + + before do + visit project_job_path(project, job) + wait_for_requests + + find('[data-testid="retry-button"]').click + end + + it 'shows a modal to warn the user' do + page.within('.modal-header') do + expect(page).to have_content 'Are you sure you want to retry this job?' + end + end + + it 'retries the job' do + find('[data-testid="retry-button-modal"]').click + + within '[data-testid="ci-header-content"]' do + expect(page).to have_content('pending') + end + end + end end describe "GET /:project/jobs/:id/download", :js do diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb index 4ff3827b240..25791b393bc 100644 --- a/spec/features/projects/navbar_spec.rb +++ b/spec/features/projects/navbar_spec.rb @@ -67,4 +67,23 @@ RSpec.describe 'Project navbar' do it_behaves_like 'verified navigation bar' end + + context 'when invite team members is not available' do + it 'does not display the js-invite-members-trigger' do + visit project_path(project) + + expect(page).not_to have_selector('.js-invite-members-trigger') + end + end + + context 'when invite team members is available' do + it 'includes the div for js-invite-members-trigger' do + stub_feature_flags(invite_members_group_modal: true) + allow_any_instance_of(InviteMembersHelper).to receive(:invite_members_allowed?).and_return(true) + + visit project_path(project) + + expect(page).to have_selector('.js-invite-members-trigger') + end + end end diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb index c3eea0195a6..11f712fde81 100644 --- a/spec/features/projects/pages_spec.rb +++ b/spec/features/projects/pages_spec.rb @@ -365,7 +365,7 @@ RSpec.shared_examples 'pages settings editing' do end let!(:artifact) do - create(:ci_job_artifact, :archive, + create(:ci_job_artifact, :archive, :correct_checksum, file: fixture_file_upload(File.join('spec/fixtures/pages.zip')), job: ci_build) end diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb index 1e2cd3c0a69..94e3331b173 100644 --- a/spec/features/projects/pipeline_schedules_spec.rb +++ b/spec/features/projects/pipeline_schedules_spec.rb @@ -24,7 +24,7 @@ RSpec.describe 'Pipeline Schedules', :js do it 'displays the required information description' do page.within('.pipeline-schedule-table-row') do expect(page).to have_content('pipeline schedule') - expect(find(".next-run-cell time")['data-original-title']) + expect(find(".next-run-cell time")['title']) .to include(pipeline_schedule.real_next_run.strftime('%b %-d, %Y')) expect(page).to have_link('master') expect(page).to have_link("##{pipeline.id}") diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 51826d867cd..ac3566fbbdd 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -14,6 +14,7 @@ RSpec.describe 'Pipeline', :js do before do sign_in(user) project.add_role(user, role) + stub_feature_flags(graphql_pipeline_details: false) end shared_context 'pipeline builds' do @@ -345,7 +346,7 @@ RSpec.describe 'Pipeline', :js do it 'shows Pipeline, Jobs, DAG and Failed Jobs tabs with link' do expect(page).to have_link('Pipeline') expect(page).to have_link('Jobs') - expect(page).to have_link('DAG') + expect(page).to have_link('Needs') expect(page).to have_link('Failed Jobs') end @@ -892,7 +893,7 @@ RSpec.describe 'Pipeline', :js do it 'shows Pipeline, Jobs and DAG tabs with link' do expect(page).to have_link('Pipeline') expect(page).to have_link('Jobs') - expect(page).to have_link('DAG') + expect(page).to have_link('Needs') end it 'shows counter in Jobs tab' do diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 3e78dfc3bc7..450524b8d70 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -12,6 +12,7 @@ RSpec.describe 'Pipelines', :js do before do sign_in(user) + stub_feature_flags(graphql_pipeline_details: false) project.add_developer(user) project.update!(auto_devops_attributes: { enabled: false }) end diff --git a/spec/features/projects/releases/user_views_edit_release_spec.rb b/spec/features/projects/releases/user_views_edit_release_spec.rb index 9115a135aeb..bb54b6be9c4 100644 --- a/spec/features/projects/releases/user_views_edit_release_spec.rb +++ b/spec/features/projects/releases/user_views_edit_release_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe 'User edits Release', :js do let_it_be(:project) { create(:project, :repository) } - let_it_be(:release) { create(:release, project: project, name: 'The first release' ) } + let_it_be(:release) { create(:release, :with_milestones, milestones_count: 1, project: project, name: 'The first release' ) } let_it_be(:user) { create(:user) } before do diff --git a/spec/features/projects/releases/user_views_releases_spec.rb b/spec/features/projects/releases/user_views_releases_spec.rb index 323c57570c3..aabbc8cea7b 100644 --- a/spec/features/projects/releases/user_views_releases_spec.rb +++ b/spec/features/projects/releases/user_views_releases_spec.rb @@ -3,8 +3,14 @@ require 'spec_helper' RSpec.describe 'User views releases', :js do + let_it_be(:today) { Time.zone.now } + let_it_be(:yesterday) { today - 1.day } + let_it_be(:tomorrow) { today + 1.day } + let_it_be(:project) { create(:project, :repository, :private) } - let_it_be(:release) { create(:release, project: project, name: 'The first release' ) } + let_it_be(:release_v1) { create(:release, project: project, tag: 'v1', name: 'The first release', released_at: yesterday, created_at: today) } + let_it_be(:release_v2) { create(:release, project: project, tag: 'v2-with-a/slash', name: 'The second release', released_at: today, created_at: yesterday) } + let_it_be(:release_v3) { create(:release, project: project, tag: 'v3', name: 'The third release', released_at: tomorrow, created_at: tomorrow) } let_it_be(:maintainer) { create(:user) } let_it_be(:guest) { create(:user) } @@ -17,38 +23,36 @@ RSpec.describe 'User views releases', :js do context('when the user is a maintainer') do before do sign_in(maintainer) - end - it 'sees the release' do visit project_releases_path(project) + end - expect(page).to have_content(release.name) - expect(page).to have_content(release.tag) - expect(page).not_to have_content('Upcoming Release') + it 'sees the release' do + page.within("##{release_v1.tag}") do + expect(page).to have_content(release_v1.name) + expect(page).to have_content(release_v1.tag) + expect(page).not_to have_content('Upcoming Release') + end end context 'when there is a link as an asset' do - let!(:release_link) { create(:release_link, release: release, url: url ) } + let!(:release_link) { create(:release_link, release: release_v1, url: url ) } let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" } - let(:direct_asset_link) { Gitlab::Routing.url_helpers.project_release_url(project, release) << release_link.filepath } + let(:direct_asset_link) { Gitlab::Routing.url_helpers.project_release_url(project, release_v1) << "/downloads#{release_link.filepath}" } it 'sees the link' do - visit project_releases_path(project) - - page.within('.js-assets-list') do + page.within("##{release_v1.tag} .js-assets-list") do expect(page).to have_link release_link.name, href: direct_asset_link expect(page).not_to have_css('[data-testid="external-link-indicator"]') end end context 'when there is a link redirect' do - let!(:release_link) { create(:release_link, release: release, name: 'linux-amd64 binaries', filepath: '/binaries/linux-amd64', url: url) } + let!(:release_link) { create(:release_link, release: release_v1, name: 'linux-amd64 binaries', filepath: '/binaries/linux-amd64', url: url) } let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" } it 'sees the link' do - visit project_releases_path(project) - - page.within('.js-assets-list') do + page.within("##{release_v1.tag} .js-assets-list") do expect(page).to have_link release_link.name, href: direct_asset_link expect(page).not_to have_css('[data-testid="external-link-indicator"]') end @@ -59,9 +63,7 @@ RSpec.describe 'User views releases', :js do let(:url) { 'http://google.com/download' } it 'sees that the link is external resource' do - visit project_releases_path(project) - - page.within('.js-assets-list') do + page.within("##{release_v1.tag} .js-assets-list") do expect(page).to have_css('[data-testid="external-link-indicator"]') end end @@ -69,23 +71,57 @@ RSpec.describe 'User views releases', :js do end context 'with an upcoming release' do - let(:tomorrow) { Time.zone.now + 1.day } - let!(:release) { create(:release, project: project, released_at: tomorrow ) } - it 'sees the upcoming tag' do - visit project_releases_path(project) - - expect(page).to have_content('Upcoming Release') + page.within("##{release_v3.tag}") do + expect(page).to have_content('Upcoming Release') + end end end context 'with a tag containing a slash' do it 'sees the release' do - release = create :release, project: project, tag: 'debian/2.4.0-1' - visit project_releases_path(project) + page.within("##{release_v2.tag.parameterize}") do + expect(page).to have_content(release_v2.name) + expect(page).to have_content(release_v2.tag) + end + end + end + + context 'sorting' do + def sort_page(by:, direction:) + within '[data-testid="releases-sort"]' do + find('.dropdown-toggle').click + + click_button(by, class: 'dropdown-item') + + find('.sorting-direction-button').click if direction == :ascending + end + end + + shared_examples 'releases sort order' do + it "sorts the releases #{description}" do + card_titles = page.all('.release-block .card-title', minimum: expected_releases.count) + + card_titles.each_with_index do |title, index| + expect(title).to have_content(expected_releases[index].name) + end + end + end + + context "when the page is sorted by the default sort order" do + let(:expected_releases) { [release_v3, release_v2, release_v1] } + + it_behaves_like 'releases sort order' + end + + context "when the page is sorted by created_at ascending " do + let(:expected_releases) { [release_v2, release_v1, release_v3] } + + before do + sort_page by: 'Created date', direction: :ascending + end - expect(page).to have_content(release.name) - expect(page).to have_content(release.tag) + it_behaves_like 'releases sort order' end end end @@ -98,14 +134,14 @@ RSpec.describe 'User views releases', :js do it 'renders release info except for Git-related data' do visit project_releases_path(project) - within('.release-block') do - expect(page).to have_content(release.description) + within('.release-block', match: :first) do + expect(page).to have_content(release_v3.description) # The following properties (sometimes) include Git info, # so they are not rendered for Guest users - expect(page).not_to have_content(release.name) - expect(page).not_to have_content(release.tag) - expect(page).not_to have_content(release.commit.short_id) + expect(page).not_to have_content(release_v3.name) + expect(page).not_to have_content(release_v3.tag) + expect(page).not_to have_content(release_v3.commit.short_id) end end end diff --git a/spec/features/projects/settings/registry_settings_spec.rb b/spec/features/projects/settings/registry_settings_spec.rb index 4e1b53ffc87..cb333bdb428 100644 --- a/spec/features/projects/settings/registry_settings_spec.rb +++ b/spec/features/projects/settings/registry_settings_spec.rb @@ -15,10 +15,10 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p before do project.update!(container_registry_enabled: container_registry_enabled_on_project) + project.container_expiration_policy.update!(enabled: true) sign_in(user) stub_container_registry_config(enabled: container_registry_enabled) - stub_feature_flags(new_variables_ui: false) end context 'as owner' do @@ -33,13 +33,13 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p subject within '#js-registry-policies' do - within '.card-body' do + within '.gl-card-body' do select('7 days until tags are automatically removed', from: 'Expiration interval:') select('Every day', from: 'Expiration schedule:') select('50 tags per image name', from: 'Number of tags to retain:') fill_in('Tags with names matching this regex pattern will expire:', with: '.*-production') end - submit_button = find('.card-footer .btn.btn-success') + submit_button = find('.gl-card-footer .btn.btn-success') expect(submit_button).not_to be_disabled submit_button.click end @@ -51,10 +51,10 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p subject within '#js-registry-policies' do - within '.card-body' do + within '.gl-card-body' do fill_in('Tags with names matching this regex pattern will expire:', with: '*-production') end - submit_button = find('.card-footer .btn.btn-success') + submit_button = find('.gl-card-footer .btn.btn-success') expect(submit_button).not_to be_disabled submit_button.click end @@ -85,7 +85,7 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p within '#js-registry-policies' do case result when :available_section - expect(find('.card-header')).to have_content('Tag expiration policy') + expect(find('.gl-card-header')).to have_content('Tag expiration policy') when :disabled_message expect(find('.gl-alert-title')).to have_content('Cleanup policy for tags is disabled') end diff --git a/spec/features/projects/settings/service_desk_setting_spec.rb b/spec/features/projects/settings/service_desk_setting_spec.rb index 7856ab1fb4e..59e6f54da2f 100644 --- a/spec/features/projects/settings/service_desk_setting_spec.rb +++ b/spec/features/projects/settings/service_desk_setting_spec.rb @@ -28,6 +28,6 @@ RSpec.describe 'Service Desk Setting', :js do project.reload expect(project.service_desk_enabled).to be_truthy expect(project.service_desk_address).to be_present - expect(find('.incoming-email').value).to eq(project.service_desk_address) + expect(find('[data-testid="incoming-email"]').value).to eq(project.service_desk_address) end end diff --git a/spec/features/projects/settings/webhooks_settings_spec.rb b/spec/features/projects/settings/webhooks_settings_spec.rb index d184f08bd89..528fd58cbe6 100644 --- a/spec/features/projects/settings/webhooks_settings_spec.rb +++ b/spec/features/projects/settings/webhooks_settings_spec.rb @@ -45,6 +45,7 @@ RSpec.describe 'Projects > Settings > Webhook Settings' do expect(page).to have_content('Merge requests events') expect(page).to have_content('Pipeline events') expect(page).to have_content('Wiki page events') + expect(page).to have_content('Releases events') end it 'create webhook' do diff --git a/spec/features/projects/show/schema_markup_spec.rb b/spec/features/projects/show/schema_markup_spec.rb new file mode 100644 index 00000000000..e651798af23 --- /dev/null +++ b/spec/features/projects/show/schema_markup_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Projects > Show > Schema Markup' do + let_it_be(:project) { create(:project, :repository, :public, :with_avatar, description: 'foobar', tag_list: 'tag1, tag2') } + + it 'shows SoftwareSourceCode structured markup', :js do + visit project_path(project) + wait_for_all_requests + + aggregate_failures do + expect(page).to have_selector('[itemscope][itemtype="http://schema.org/SoftwareSourceCode"]') + expect(page).to have_selector('img[itemprop="image"]') + expect(page).to have_selector('[itemprop="name"]', text: project.name) + expect(page).to have_selector('[itemprop="identifier"]', text: "Project ID: #{project.id}") + expect(page).to have_selector('[itemprop="abstract"]', text: project.description) + expect(page).to have_selector('[itemprop="license"]', text: project.repository.license.name) + expect(find_all('[itemprop="keywords"]').map(&:text)).to match_array(project.tag_list.map(&:capitalize)) + expect(page).to have_selector('[itemprop="about"]') + end + end +end diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb index 28fe0a0b7e1..0ed9e23c7f8 100644 --- a/spec/features/projects/snippets/create_snippet_spec.rb +++ b/spec/features/projects/snippets/create_snippet_spec.rb @@ -62,7 +62,7 @@ RSpec.describe 'Projects > Snippets > Create Snippet', :js do click_button('Create snippet') wait_for_requests - link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] + link = find('a.no-attachment-icon img.js-lazy-loaded[alt="banana_sample"]')['src'] expect(link).to match(%r{/#{Regexp.escape(project.full_path)}/uploads/\h{32}/banana_sample\.gif\z}) end diff --git a/spec/features/projects/terraform_spec.rb b/spec/features/projects/terraform_spec.rb new file mode 100644 index 00000000000..2680dfb2b13 --- /dev/null +++ b/spec/features/projects/terraform_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Terraform', :js do + let_it_be(:project) { create(:project) } + + let(:user) { project.creator } + + before do + gitlab_sign_in(user) + end + + context 'when user does not have any terraform states and visits index page' do + before do + visit project_terraform_index_path(project) + end + + it 'sees an empty state' do + expect(page).to have_content('Get started with Terraform') + end + end + + context 'when user has a terraform state' do + let_it_be(:terraform_state) { create(:terraform_state, :locked, project: project) } + + context 'when user visits the index page' do + before do + visit project_terraform_index_path(project) + end + + it 'displays a tab with states count' do + expect(page).to have_content("States #{project.terraform_states.size}") + end + + it 'displays a table with terraform states' do + expect(page).to have_selector( + '[data-testid="terraform-states-table"] tbody tr', + count: project.terraform_states.size + ) + end + + it 'displays terraform information' do + expect(page).to have_content(terraform_state.name) + end + end + end +end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 6baeb4ce368..9b5f4ca6d48 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -279,7 +279,7 @@ RSpec.describe 'Project' do end it 'deletes a project', :sidekiq_might_not_need_inline do - expect { remove_with_confirm('Delete project', "Delete #{project.full_name}", 'Yes, delete project') }.to change { Project.count }.by(-1) + expect { remove_with_confirm('Delete project', project.path, 'Yes, delete project') }.to change { Project.count }.by(-1) expect(page).to have_content "Project '#{project.full_name}' is in the process of being deleted." expect(Project.all.count).to be_zero expect(project.issues).to be_empty diff --git a/spec/features/read_only_spec.rb b/spec/features/read_only_spec.rb index eb043d2193a..11686552062 100644 --- a/spec/features/read_only_spec.rb +++ b/spec/features/read_only_spec.rb @@ -9,19 +9,19 @@ RSpec.describe 'read-only message' do sign_in(user) end - it 'shows read-only banner when database is read-only' do - allow(Gitlab::Database).to receive(:read_only?).and_return(true) + context 'when database is read-only' do + before do + allow(Gitlab::Database).to receive(:read_only?).and_return(true) + end - visit root_dashboard_path - - expect(page).to have_content('You are on a read-only GitLab instance.') + it_behaves_like 'Read-only instance', /You are on a read\-only GitLab instance./ end - it 'does not show read-only banner when database is able to read-write' do - allow(Gitlab::Database).to receive(:read_only?).and_return(false) - - visit root_dashboard_path + context 'when database is in read-write mode' do + before do + allow(Gitlab::Database).to receive(:read_only?).and_return(false) + end - expect(page).not_to have_content('You are on a read-only GitLab instance.') + it_behaves_like 'Read-write instance', /You are on a read\-only GitLab instance./ end end diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index 6e18de3be7b..9697e10c3d1 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -122,6 +122,19 @@ RSpec.describe 'Runners' do end end + context 'when multiple runners are configured' do + let!(:specific_runner) { create(:ci_runner, :project, projects: [project]) } + let!(:specific_runner_2) { create(:ci_runner, :project, projects: [project]) } + + it 'adds pagination to the runner list' do + stub_const('Projects::Settings::CiCdController::NUMBER_OF_RUNNERS_PER_PAGE', 1) + + visit project_runners_path(project) + + expect(find('.pagination')).not_to be_nil + end + end + context 'when a specific runner exists in another project' do let(:another_project) { create(:project) } let!(:specific_runner) { create(:ci_runner, :project, projects: [another_project]) } diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb index a88043c98ac..f761bd30baf 100644 --- a/spec/features/search/user_searches_for_code_spec.rb +++ b/spec/features/search/user_searches_for_code_spec.rb @@ -28,10 +28,7 @@ RSpec.describe 'User searches for code' do before do visit(search_path) find('.js-search-project-dropdown').click - - page.within('.project-filter') do - click_link(project.full_name) - end + find('[data-testid="project-filter"]').click_link(project.full_name) end include_examples 'top right search form' diff --git a/spec/features/search/user_searches_for_issues_spec.rb b/spec/features/search/user_searches_for_issues_spec.rb index 900ed35adea..e2ae2738d2f 100644 --- a/spec/features/search/user_searches_for_issues_spec.rb +++ b/spec/features/search/user_searches_for_issues_spec.rb @@ -5,8 +5,8 @@ require 'spec_helper' RSpec.describe 'User searches for issues', :js do let(:user) { create(:user) } let(:project) { create(:project, namespace: user.namespace) } - let!(:issue1) { create(:issue, title: 'Foo', project: project) } - let!(:issue2) { create(:issue, :closed, :confidential, title: 'Bar', project: project) } + let!(:issue1) { create(:issue, title: 'issue Foo', project: project, created_at: 1.hour.ago) } + let!(:issue2) { create(:issue, :closed, :confidential, title: 'issue Bar', project: project) } def search_for_issue(search) fill_in('dashboard_search', with: search) @@ -67,13 +67,26 @@ RSpec.describe 'User searches for issues', :js do end end + it 'sorts by created date' do + search_for_issue('issue') + + page.within('.results') do + expect(page.all('.search-result-row').first).to have_link(issue2.title) + expect(page.all('.search-result-row').last).to have_link(issue1.title) + end + + find('.reverse-sort-btn').click + + page.within('.results') do + expect(page.all('.search-result-row').first).to have_link(issue1.title) + expect(page.all('.search-result-row').last).to have_link(issue2.title) + end + end + context 'when on a project page' do it 'finds an issue' do find('.js-search-project-dropdown').click - - page.within('.project-filter') do - click_link(project.full_name) - end + find('[data-testid="project-filter"]').click_link(project.full_name) search_for_issue(issue1.title) diff --git a/spec/features/search/user_searches_for_merge_requests_spec.rb b/spec/features/search/user_searches_for_merge_requests_spec.rb index 40583664958..6f8f6303b66 100644 --- a/spec/features/search/user_searches_for_merge_requests_spec.rb +++ b/spec/features/search/user_searches_for_merge_requests_spec.rb @@ -31,10 +31,7 @@ RSpec.describe 'User searches for merge requests', :js do context 'when on a project page' do it 'finds a merge request' do find('.js-search-project-dropdown').click - - page.within('.project-filter') do - click_link(project.full_name) - end + find('[data-testid="project-filter"]').click_link(project.full_name) fill_in('dashboard_search', with: merge_request1.title) find('.btn-search').click diff --git a/spec/features/search/user_searches_for_milestones_spec.rb b/spec/features/search/user_searches_for_milestones_spec.rb index 64e756db180..1a2227db214 100644 --- a/spec/features/search/user_searches_for_milestones_spec.rb +++ b/spec/features/search/user_searches_for_milestones_spec.rb @@ -31,10 +31,7 @@ RSpec.describe 'User searches for milestones', :js do context 'when on a project page' do it 'finds a milestone' do find('.js-search-project-dropdown').click - - page.within('.project-filter') do - click_link(project.full_name) - end + find('[data-testid="project-filter"]').click_link(project.full_name) fill_in('dashboard_search', with: milestone1.title) find('.btn-search').click diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb index fc60b6244d9..6bf1407fd4f 100644 --- a/spec/features/search/user_searches_for_wiki_pages_spec.rb +++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb @@ -19,10 +19,7 @@ RSpec.describe 'User searches for wiki pages', :js do shared_examples 'search wiki blobs' do it 'finds a page' do find('.js-search-project-dropdown').click - - page.within('.project-filter') do - click_link(project.full_name) - end + find('[data-testid="project-filter"]').click_link(project.full_name) fill_in('dashboard_search', with: search_term) find('.btn-search').click diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb index 5cbfacf4e48..9296a3f33d4 100644 --- a/spec/features/search/user_uses_header_search_field_spec.rb +++ b/spec/features/search/user_uses_header_search_field_spec.rb @@ -5,11 +5,18 @@ require 'spec_helper' RSpec.describe 'User uses header search field', :js do include FilteredSearchHelpers - let(:project) { create(:project) } - let(:user) { create(:user) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:reporter) { create(:user) } + let_it_be(:developer) { create(:user) } + + let(:user) { reporter } + + before_all do + project.add_reporter(reporter) + project.add_developer(developer) + end before do - project.add_reporter(user) sign_in(user) end @@ -34,7 +41,7 @@ RSpec.describe 'User uses header search field', :js do wait_for_all_requests end - it 'shows the category search dropdown' do + it 'shows the category search dropdown', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/250285' do expect(page).to have_selector('.dropdown-header', text: /#{scope_name}/i) end end @@ -44,7 +51,7 @@ RSpec.describe 'User uses header search field', :js do page.find('#search').click end - it 'shows category search dropdown' do + it 'shows category search dropdown', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/250285' do expect(page).to have_selector('.dropdown-header', text: /#{scope_name}/i) end @@ -104,7 +111,7 @@ RSpec.describe 'User uses header search field', :js do let(:scope_name) { 'All GitLab' } end - it 'displays search options' do + it 'displays search options', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/251076' do fill_in_search('test') expect(page).to have_selector(scoped_search_link('test')) @@ -132,6 +139,10 @@ RSpec.describe 'User uses header search field', :js do let(:group) { create(:group) } let(:project) { create(:project, namespace: group) } + before do + project.add_reporter(user) + end + include_examples 'search field examples' do let(:url) { project_path(project) } let(:scope_name) { project.name } @@ -159,6 +170,35 @@ RSpec.describe 'User uses header search field', :js do expect(page).not_to have_selector(scoped_search_link('test', group_id: project.namespace_id)) expect(page).to have_selector(scoped_search_link('test', project_id: project.id)) end + + it 'displays a link to project merge requests' do + fill_in_search('Merge') + + within(dashboard_search_options_popup_menu) do + expect(page).to have_text('Merge Requests') + end + end + + it 'does not display a link to project feature flags' do + fill_in_search('Feature') + + within(dashboard_search_options_popup_menu) do + expect(page).to have_text('"Feature" in all GitLab') + expect(page).to have_no_text('Feature Flags') + end + end + + context 'and user is a developer' do + let(:user) { developer } + + it 'displays a link to project feature flags' do + fill_in_search('Feature') + + within(dashboard_search_options_popup_menu) do + expect(page).to have_text('Feature Flags') + end + end + end end end @@ -217,4 +257,8 @@ RSpec.describe 'User uses header search field', :js do ".dropdown a[href='#{href}']" end + + def dashboard_search_options_popup_menu + "div[data-testid='dashboard-search-options']" + end end diff --git a/spec/features/search/user_uses_search_filters_spec.rb b/spec/features/search/user_uses_search_filters_spec.rb index 080cced21c3..bd77e6003e3 100644 --- a/spec/features/search/user_uses_search_filters_spec.rb +++ b/spec/features/search/user_uses_search_filters_spec.rb @@ -18,17 +18,17 @@ RSpec.describe 'User uses search filters', :js do it 'shows group projects' do visit search_path - find('.js-search-group-dropdown').click + find('[data-testid="group-filter"]').click wait_for_requests - page.within('.search-page-form') do - click_link(group.name) + page.within('[data-testid="group-filter"]') do + click_on(group.name) end - expect(find('.js-search-group-dropdown')).to have_content(group.name) + expect(find('[data-testid="group-filter"]')).to have_content(group.name) - page.within('.project-filter') do + page.within('[data-testid="project-filter"]') do find('.js-search-project-dropdown').click wait_for_requests @@ -44,10 +44,11 @@ RSpec.describe 'User uses search filters', :js do describe 'clear filter button' do it 'removes Group and Project filters' do - link = find('[data-testid="group-filter"] .js-search-clear') - params = CGI.parse(URI.parse(link[:href]).query) + find('[data-testid="group-filter"] [data-testid="clear-icon"]').click + + wait_for_requests - expect(params).not_to include(:group_id, :project_id) + expect(page).to have_current_path(search_path(search: "test")) end end end @@ -57,7 +58,7 @@ RSpec.describe 'User uses search filters', :js do it 'shows a project' do visit search_path - page.within('.project-filter') do + page.within('[data-testid="project-filter"]') do find('.js-search-project-dropdown').click wait_for_requests @@ -77,7 +78,7 @@ RSpec.describe 'User uses search filters', :js do describe 'clear filter button' do it 'removes Project filters' do - link = find('.project-filter .js-search-clear') + link = find('[data-testid="project-filter"] .js-search-clear') params = CGI.parse(URI.parse(link[:href]).query) expect(params).not_to include(:project_id) diff --git a/spec/features/static_site_editor_spec.rb b/spec/features/static_site_editor_spec.rb index 03085917d67..a47579582e2 100644 --- a/spec/features/static_site_editor_spec.rb +++ b/spec/features/static_site_editor_spec.rb @@ -73,4 +73,44 @@ RSpec.describe 'Static Site Editor' do expect(node['data-static-site-generator']).to eq('middleman') end end + + describe 'Static Site Editor Content Security Policy' do + subject { response_headers['Content-Security-Policy'] } + + context 'when no global CSP config exists' do + before do + expect_next_instance_of(Projects::StaticSiteEditorController) do |controller| + expect(controller).to receive(:current_content_security_policy) + .and_return(ActionDispatch::ContentSecurityPolicy.new) + end + end + + it 'does not add CSP directives' do + visit sse_path + + is_expected.to be_blank + end + end + + context 'when a global CSP config exists' do + let_it_be(:cdn_url) { 'https://some-cdn.test' } + let_it_be(:youtube_url) { 'https://www.youtube.com' } + + before do + csp = ActionDispatch::ContentSecurityPolicy.new do |p| + p.frame_src :self, cdn_url + end + + expect_next_instance_of(Projects::StaticSiteEditorController) do |controller| + expect(controller).to receive(:current_content_security_policy).and_return(csp) + end + end + + it 'appends youtube to the CSP frame-src policy' do + visit sse_path + + is_expected.to eql("frame-src 'self' #{cdn_url} #{youtube_url}") + end + end + end end diff --git a/spec/features/uploads/user_uploads_file_to_note_spec.rb b/spec/features/uploads/user_uploads_file_to_note_spec.rb index 7f55ddc1d64..589cc9f9b02 100644 --- a/spec/features/uploads/user_uploads_file_to_note_spec.rb +++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb @@ -58,8 +58,8 @@ RSpec.describe 'User uploads file to note' do error_text = 'File is too big (0.06MiB). Max filesize: 0.01MiB.' expect(page).to have_selector('.uploading-error-message', visible: true, text: error_text) - expect(page).to have_selector('.retry-uploading-link', visible: true, text: 'Try again') - expect(page).to have_selector('.attach-new-file', visible: true, text: 'attach a new file') + expect(page).to have_button('Try again', visible: true) + expect(page).to have_button('attach a new file', visible: true) expect(page).not_to have_button('Attach a file') end end @@ -78,7 +78,7 @@ RSpec.describe 'User uploads file to note' do click_button 'Comment' wait_for_requests - expect(find('a.no-attachment-icon img[alt="dk"]')['src']) + expect(find('a.no-attachment-icon img.js-lazy-loaded[alt="dk"]')['src']) .to match(%r{/#{project.full_path}/uploads/\h{32}/dk\.png$}) end end diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index 853c381fe6b..0761c1871d3 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -26,7 +26,6 @@ RSpec.describe 'Login' do user.reload expect(user.reset_password_token).not_to be_nil - find('a[href="#login-pane"]').click gitlab_sign_in(user) expect(current_path).to eq root_path @@ -593,42 +592,95 @@ RSpec.describe 'Login' do describe 'UI tabs and panes' do context 'when no defaults are changed' do - it 'correctly renders tabs and panes' do - ensure_tab_pane_correctness + it 'does not render any tabs' do + visit new_user_session_path + + ensure_no_tabs + end + + it 'renders link to sign up path' do + visit new_user_session_path + + expect(page.body).to have_link('Register now', href: new_user_registration_path) end end context 'when signup is disabled' do before do stub_application_setting(signup_enabled: false) + + visit new_user_session_path end - it 'correctly renders tabs and panes' do - ensure_tab_pane_correctness + it 'does not render any tabs' do + ensure_no_tabs + end + + it 'does not render link to sign up path' do + visit new_user_session_path + + expect(page.body).not_to have_link('Register now', href: new_user_registration_path) end end context 'when ldap is enabled' do + include LdapHelpers + + let(:provider) { 'ldapmain' } + let(:ldap_server_config) do + { + 'label' => 'Main LDAP', + 'provider_name' => provider, + 'attributes' => {}, + 'encryption' => 'plain', + 'uid' => 'uid', + 'base' => 'dc=example,dc=com' + } + end + before do + stub_ldap_setting(enabled: true) + allow(::Gitlab::Auth::Ldap::Config).to receive_messages(enabled: true, servers: [ldap_server_config]) + allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [provider.to_sym]) + + Ldap::OmniauthCallbacksController.define_providers! + Rails.application.reload_routes! + + allow_next_instance_of(ActionDispatch::Routing::RoutesProxy) do |instance| + allow(instance).to receive(:"user_#{provider}_omniauth_callback_path") + .and_return("/users/auth/#{provider}/callback") + end + visit new_user_session_path - allow(page).to receive(:form_based_providers).and_return([:ldapmain]) - allow(page).to receive(:ldap_enabled).and_return(true) end it 'correctly renders tabs and panes' do - ensure_tab_pane_correctness(false) + ensure_tab_pane_correctness(['Main LDAP', 'Standard']) + end + + it 'renders link to sign up path' do + expect(page.body).to have_link('Register now', href: new_user_registration_path) end end context 'when crowd is enabled' do before do + allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [:crowd]) + stub_application_setting(crowd_enabled: true) + + Ldap::OmniauthCallbacksController.define_providers! + Rails.application.reload_routes! + + allow_next_instance_of(ActionDispatch::Routing::RoutesProxy) do |instance| + allow(instance).to receive(:user_crowd_omniauth_authorize_path) + .and_return("/users/auth/crowd/callback") + end + visit new_user_session_path - allow(page).to receive(:form_based_providers).and_return([:crowd]) - allow(page).to receive(:crowd_enabled?).and_return(true) end it 'correctly renders tabs and panes' do - ensure_tab_pane_correctness(false) + ensure_tab_pane_correctness(%w(Crowd Standard)) end end end diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb index 466b7361da9..aebe2cc602d 100644 --- a/spec/features/users/show_spec.rb +++ b/spec/features/users/show_spec.rb @@ -5,11 +5,13 @@ require 'spec_helper' RSpec.describe 'User page' do include ExternalAuthorizationServiceHelpers - let(:user) { create(:user, bio: '**Lorem** _ipsum_ dolor sit [amet](https://example.com)') } + let_it_be(:user) { create(:user, bio: '**Lorem** _ipsum_ dolor sit [amet](https://example.com)') } + + subject { visit(user_path(user)) } context 'with public profile' do it 'shows all the tabs' do - visit(user_path(user)) + subject page.within '.nav-links' do expect(page).to have_link('Overview') @@ -22,14 +24,12 @@ RSpec.describe 'User page' do end it 'does not show private profile message' do - visit(user_path(user)) + subject expect(page).not_to have_content("This user has a private profile") end context 'work information' do - subject { visit(user_path(user)) } - it 'shows job title and organization details' do user.update(organization: 'GitLab - work info test', job_title: 'Frontend Engineer') @@ -57,24 +57,24 @@ RSpec.describe 'User page' do end context 'with private profile' do - let(:user) { create(:user, private_profile: true) } + let_it_be(:user) { create(:user, private_profile: true) } it 'shows no tab' do - visit(user_path(user)) + subject expect(page).to have_css("div.profile-header") expect(page).not_to have_css("ul.nav-links") end it 'shows private profile message' do - visit(user_path(user)) + subject expect(page).to have_content("This user has a private profile") end it 'shows own tabs' do sign_in(user) - visit(user_path(user)) + subject page.within '.nav-links' do expect(page).to have_link('Overview') @@ -88,36 +88,36 @@ RSpec.describe 'User page' do end context 'with blocked profile' do - let(:user) { create(:user, state: :blocked) } + let_it_be(:user) { create(:user, state: :blocked) } it 'shows no tab' do - visit(user_path(user)) + subject expect(page).to have_css("div.profile-header") expect(page).not_to have_css("ul.nav-links") end it 'shows blocked message' do - visit(user_path(user)) + subject expect(page).to have_content("This user is blocked") end it 'shows user name as blocked' do - visit(user_path(user)) + subject expect(page).to have_css(".cover-title", text: 'Blocked user') end it 'shows no additional fields' do - visit(user_path(user)) + subject expect(page).not_to have_css(".profile-user-bio") expect(page).not_to have_css(".profile-link-holder") end it 'shows username' do - visit(user_path(user)) + subject expect(page).to have_content("@#{user.username}") end @@ -126,7 +126,7 @@ RSpec.describe 'User page' do it 'shows the status if there was one' do create(:user_status, user: user, message: "Working hard!") - visit(user_path(user)) + subject expect(page).to have_content("Working hard!") end @@ -135,7 +135,7 @@ RSpec.describe 'User page' do it 'shows the sign in link' do stub_application_setting(signup_enabled: false) - visit(user_path(user)) + subject page.within '.navbar-nav' do expect(page).to have_link('Sign in') @@ -147,7 +147,7 @@ RSpec.describe 'User page' do it 'shows the sign in and register link' do stub_application_setting(signup_enabled: true) - visit(user_path(user)) + subject page.within '.navbar-nav' do expect(page).to have_link('Sign in / Register') @@ -157,7 +157,7 @@ RSpec.describe 'User page' do context 'most recent activity' do it 'shows the most recent activity' do - visit(user_path(user)) + subject expect(page).to have_content('Most Recent Activity') end @@ -168,7 +168,7 @@ RSpec.describe 'User page' do end it 'hides the most recent activity' do - visit(user_path(user)) + subject expect(page).not_to have_content('Most Recent Activity') end @@ -177,14 +177,14 @@ RSpec.describe 'User page' do context 'page description' do before do - visit(user_path(user)) + subject end it_behaves_like 'page meta description', 'Lorem ipsum dolor sit amet' end context 'with a bot user' do - let(:user) { create(:user, user_type: :security_bot) } + let_it_be(:user) { create(:user, user_type: :security_bot) } describe 'feature flag enabled' do before do @@ -192,7 +192,7 @@ RSpec.describe 'User page' do end it 'only shows Overview and Activity tabs' do - visit(user_path(user)) + subject page.within '.nav-links' do expect(page).to have_link('Overview') @@ -211,7 +211,7 @@ RSpec.describe 'User page' do end it 'only shows Overview and Activity tabs' do - visit(user_path(user)) + subject page.within '.nav-links' do expect(page).to have_link('Overview') @@ -224,4 +224,24 @@ RSpec.describe 'User page' do end end end + + context 'structured markup' do + let_it_be(:user) { create(:user, website_url: 'https://gitlab.com', organization: 'GitLab', job_title: 'Frontend Engineer', email: 'public@example.com', public_email: 'public@example.com', location: 'Country', created_at: Time.now, updated_at: Time.now) } + + it 'shows Person structured markup' do + subject + + aggregate_failures do + expect(page).to have_selector('[itemscope][itemtype="http://schema.org/Person"]') + expect(page).to have_selector('img[itemprop="image"]') + expect(page).to have_selector('[itemprop="name"]') + expect(page).to have_selector('[itemprop="address"][itemscope][itemtype="https://schema.org/PostalAddress"]') + expect(page).to have_selector('[itemprop="addressLocality"]') + expect(page).to have_selector('[itemprop="url"]') + expect(page).to have_selector('[itemprop="email"]') + expect(page).to have_selector('span[itemprop="jobTitle"]') + expect(page).to have_selector('span[itemprop="worksFor"]') + end + end + end end diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb index c59121626f0..bfdd1e1bdb7 100644 --- a/spec/features/users/signup_spec.rb +++ b/spec/features/users/signup_spec.rb @@ -2,9 +2,51 @@ require 'spec_helper' -RSpec.shared_examples 'Signup' do +RSpec.shared_examples 'Signup name validation' do |field, max_length, label| + before do + visit new_user_registration_path + end + + describe "#{field} validation", :js do + it "does not show an error border if the user's fullname length is not longer than #{max_length} characters" do + fill_in field, with: 'u' * max_length + + expect(find('.name')).not_to have_css '.gl-field-error-outline' + end + + it 'shows an error border if the user\'s fullname contains an emoji' do + simulate_input("##{field}", 'Ehsan 🦋') + + expect(find('.name')).to have_css '.gl-field-error-outline' + end + + it "shows an error border if the user\'s fullname is longer than #{max_length} characters" do + fill_in field, with: 'n' * (max_length + 1) + + expect(find('.name')).to have_css '.gl-field-error-outline' + end + + it "shows an error message if the user\'s #{label} is longer than #{max_length} characters" do + fill_in field, with: 'n' * (max_length + 1) + + expect(page).to have_content("#{label} is too long (maximum is #{max_length} characters).") + end + + it 'shows an error message if the username contains emojis' do + simulate_input("##{field}", 'Ehsan 🦋') + + expect(page).to have_content("Invalid input, please avoid emojis") + end + end +end + +RSpec.describe 'Signup' do include TermsHelper + before do + stub_application_setting(require_admin_approval_after_user_signup: false) + end + let(:new_user) { build_stubbed(:user) } def fill_in_signup_form @@ -190,6 +232,22 @@ RSpec.shared_examples 'Signup' do expect(current_path).to eq users_sign_up_welcome_path end end + + context 'with required admin approval enabled' do + before do + stub_application_setting(require_admin_approval_after_user_signup: true) + end + + it 'creates the user but does not sign them in' do + visit new_user_registration_path + + fill_in_signup_form + + expect { click_button 'Register' }.to change { User.count }.by(1) + expect(current_path).to eq new_user_session_path + expect(page).to have_content("You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your GitLab administrator") + end + end end context 'with errors' do @@ -295,64 +353,7 @@ RSpec.shared_examples 'Signup' do expect(created_user.setup_for_company).to be_nil expect(page).to have_current_path(new_project_path) end -end - -RSpec.shared_examples 'Signup name validation' do |field, max_length, label| - before do - visit new_user_registration_path - end - - describe "#{field} validation", :js do - it "does not show an error border if the user's fullname length is not longer than #{max_length} characters" do - fill_in field, with: 'u' * max_length - - expect(find('.name')).not_to have_css '.gl-field-error-outline' - end - - it 'shows an error border if the user\'s fullname contains an emoji' do - simulate_input("##{field}", 'Ehsan 🦋') - - expect(find('.name')).to have_css '.gl-field-error-outline' - end - - it "shows an error border if the user\'s fullname is longer than #{max_length} characters" do - fill_in field, with: 'n' * (max_length + 1) - - expect(find('.name')).to have_css '.gl-field-error-outline' - end - - it "shows an error message if the user\'s #{label} is longer than #{max_length} characters" do - fill_in field, with: 'n' * (max_length + 1) - - expect(page).to have_content("#{label} is too long (maximum is #{max_length} characters).") - end - - it 'shows an error message if the username contains emojis' do - simulate_input("##{field}", 'Ehsan 🦋') - - expect(page).to have_content("Invalid input, please avoid emojis") - end - end -end - -RSpec.describe 'With original flow' do - before do - stub_experiment(signup_flow: false) - stub_experiment_for_user(signup_flow: false) - end - - it_behaves_like 'Signup' - it_behaves_like 'Signup name validation', 'new_user_first_name', 127, 'First name' - it_behaves_like 'Signup name validation', 'new_user_last_name', 127, 'Last name' -end - -RSpec.describe 'With experimental flow' do - before do - stub_experiment(signup_flow: true) - stub_experiment_for_user(signup_flow: true) - end - it_behaves_like 'Signup' it_behaves_like 'Signup name validation', 'new_user_first_name', 127, 'First name' it_behaves_like 'Signup name validation', 'new_user_last_name', 127, 'Last name' end |