From 4555e1b21c365ed8303ffb7a3325d773c9b8bf31 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 19 May 2021 15:44:42 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-12-stable-ee --- spec/features/action_cable_logging_spec.rb | 6 +- spec/features/admin/admin_appearance_spec.rb | 5 +- spec/features/admin/admin_dev_ops_report_spec.rb | 6 +- spec/features/admin/admin_groups_spec.rb | 10 +- spec/features/admin/admin_labels_spec.rb | 2 +- spec/features/admin/admin_mode/logout_spec.rb | 70 ++- spec/features/admin/admin_mode_spec.rb | 235 +++++---- spec/features/admin/admin_projects_spec.rb | 36 +- spec/features/admin/admin_settings_spec.rb | 73 ++- spec/features/admin/admin_users_spec.rb | 44 +- .../admin_visits_service_templates_spec.rb | 2 +- spec/features/admin/users/user_spec.rb | 177 ++++--- spec/features/admin/users/users_spec.rb | 586 +++++++++++---------- spec/features/boards/boards_spec.rb | 49 +- spec/features/boards/new_issue_spec.rb | 93 ++-- spec/features/boards/sidebar_assignee_spec.rb | 25 +- .../boards/sidebar_labels_in_namespaces_spec.rb | 34 ++ spec/features/boards/sub_group_project_spec.rb | 46 -- spec/features/boards/user_visits_board_spec.rb | 78 +++ spec/features/calendar_spec.rb | 4 +- spec/features/dashboard/active_tab_spec.rb | 56 +- ...ard_with_external_authorization_service_spec.rb | 74 ++- spec/features/dashboard/shortcuts_spec.rb | 106 ++-- spec/features/dashboard/todos/todos_spec.rb | 2 +- .../frequently_visited_projects_and_groups_spec.rb | 76 ++- ...age_with_external_authorization_service_spec.rb | 6 +- spec/features/groups/issues_spec.rb | 2 +- spec/features/groups/members/manage_groups_spec.rb | 42 +- .../features/groups/members/manage_members_spec.rb | 110 +++- ...master_adds_member_with_expiration_date_spec.rb | 12 +- spec/features/groups/milestone_spec.rb | 4 +- .../groups/milestones/gfm_autocomplete_spec.rb | 80 +++ spec/features/groups/navbar_spec.rb | 62 ++- .../settings/packages_and_registries_spec.rb | 31 +- spec/features/groups_spec.rb | 41 +- spec/features/invites_spec.rb | 319 ++++++----- spec/features/issuables/sorting_list_spec.rb | 36 +- .../features/issues/bulk_assignment_labels_spec.rb | 475 ----------------- ..._issue_for_discussions_in_merge_request_spec.rb | 2 +- .../issues/filtered_search/filter_issues_spec.rb | 22 +- spec/features/issues/issue_sidebar_spec.rb | 81 +-- spec/features/issues/resource_label_events_spec.rb | 2 +- spec/features/issues/service_desk_spec.rb | 4 +- spec/features/issues/spam_issues_spec.rb | 1 + spec/features/issues/update_issues_spec.rb | 128 ----- .../issues/user_bulk_edits_issues_labels_spec.rb | 468 ++++++++++++++++ .../features/issues/user_bulk_edits_issues_spec.rb | 128 +++++ spec/features/issues/user_edits_issue_spec.rb | 8 +- .../issues/user_interacts_with_awards_spec.rb | 53 +- .../issues/user_toggles_subscription_spec.rb | 4 +- spec/features/markdown/copy_as_gfm_spec.rb | 4 +- spec/features/markdown/math_spec.rb | 18 +- spec/features/merge_request/batch_comments_spec.rb | 4 +- .../user_creates_merge_request_spec.rb | 1 + .../user_merges_when_pipeline_succeeds_spec.rb | 6 +- .../merge_request/user_posts_notes_spec.rb | 1 + .../merge_request/user_resolves_conflicts_spec.rb | 2 +- ...lves_diff_notes_and_discussions_resolve_spec.rb | 2 +- .../user_sees_cherry_pick_modal_spec.rb | 2 +- ...ton_depending_on_unresolved_discussions_spec.rb | 2 +- .../user_sees_merge_request_pipelines_spec.rb | 8 +- .../merge_request/user_sees_merge_widget_spec.rb | 2 +- ...ser_sees_mr_from_deleted_forked_project_spec.rb | 2 +- .../merge_request/user_sees_pipelines_spec.rb | 2 +- .../user_sees_wip_help_message_spec.rb | 2 +- .../user_views_open_merge_request_spec.rb | 2 +- .../merge_requests/user_mass_updates_spec.rb | 32 +- spec/features/monitor_sidebar_link_spec.rb | 145 +++++ spec/features/operations_sidebar_link_spec.rb | 144 ----- .../populate_new_pipeline_vars_with_params_spec.rb | 17 +- spec/features/profiles/chat_names_spec.rb | 6 +- .../profiles/user_edit_preferences_spec.rb | 19 +- spec/features/profiles/user_edit_profile_spec.rb | 24 - .../profiles/user_visits_notifications_tab_spec.rb | 8 + spec/features/project_variables_spec.rb | 2 +- spec/features/projects/active_tabs_spec.rb | 58 +- .../projects/badges/pipeline_badge_spec.rb | 1 + spec/features/projects/blobs/blob_show_spec.rb | 75 +++ .../projects/branches/user_deletes_branch_spec.rb | 1 + spec/features/projects/commit/builds_spec.rb | 2 +- spec/features/projects/commit/cherry_pick_spec.rb | 1 + .../commit/user_comments_on_commit_spec.rb | 1 + .../projects/commit/user_reverts_commit_spec.rb | 1 + .../user_views_user_status_on_commit_spec.rb | 1 + spec/features/projects/compare_spec.rb | 28 + .../confluence/user_views_confluence_page_spec.rb | 1 + spec/features/projects/deploy_keys_spec.rb | 5 +- spec/features/projects/diffs/diff_show_spec.rb | 2 +- spec/features/projects/features_visibility_spec.rb | 17 +- .../files/gitlab_ci_syntax_yml_dropdown_spec.rb | 69 --- .../projects/files/user_edits_files_spec.rb | 4 +- spec/features/projects/fork_spec.rb | 4 +- spec/features/projects/graph_spec.rb | 2 +- .../integrations/user_activates_asana_spec.rb | 17 + .../integrations/user_activates_assembla_spec.rb | 20 + .../user_activates_atlassian_bamboo_ci_spec.rb | 29 + .../user_views_design_images_spec.rb | 1 + spec/features/projects/jobs_spec.rb | 4 +- .../labels/issues_sorted_by_priority_spec.rb | 24 +- .../labels/user_sees_links_to_issuables_spec.rb | 1 + .../projects/labels/user_views_labels_spec.rb | 1 + .../features/projects/members/invite_group_spec.rb | 106 ++-- spec/features/projects/members/list_spec.rb | 27 +- ...master_adds_member_with_expiration_date_spec.rb | 14 +- .../features/projects/merge_request_button_spec.rb | 1 + .../projects/milestones/gfm_autocomplete_spec.rb | 80 +++ spec/features/projects/navbar_spec.rb | 166 ++++-- .../projects/new_project_from_template_spec.rb | 26 + spec/features/projects/new_project_spec.rb | 560 +++++++++++--------- .../projects/pages/user_adds_domain_spec.rb | 1 + spec/features/projects/pipelines/pipeline_spec.rb | 1 + spec/features/projects/pipelines/pipelines_spec.rb | 70 +-- .../projects/product_analytics/events_spec.rb | 1 + .../projects/releases/user_views_releases_spec.rb | 172 +++--- .../projects/services/user_activates_asana_spec.rb | 17 - .../services/user_activates_assembla_spec.rb | 20 - .../user_activates_atlassian_bamboo_ci_spec.rb | 29 - .../services/user_activates_issue_tracker_spec.rb | 2 +- .../projects/settings/access_tokens_spec.rb | 2 +- .../projects/settings/monitor_settings_spec.rb | 189 +++++++ .../projects/settings/operations_settings_spec.rb | 179 ------- .../projects/settings/packages_settings_spec.rb | 26 +- .../projects/settings/project_settings_spec.rb | 1 + .../projects/settings/registry_settings_spec.rb | 160 +++--- .../projects/settings/repository_settings_spec.rb | 4 +- .../user_manages_merge_requests_settings_spec.rb | 12 +- .../settings/user_manages_project_members_spec.rb | 28 +- .../projects/snippets/user_views_snippets_spec.rb | 1 + .../user_changes_project_visibility_spec.rb | 4 +- spec/features/projects/user_sees_sidebar_spec.rb | 2 +- .../projects/user_sees_user_popover_spec.rb | 1 + spec/features/projects/user_uses_shortcuts_spec.rb | 67 ++- spec/features/runners_spec.rb | 96 +++- spec/features/snippets/spam_snippets_spec.rb | 1 + spec/features/unsubscribe_links_spec.rb | 2 +- .../user_can_display_performance_bar_spec.rb | 36 +- .../users/add_email_to_existing_account_spec.rb | 18 +- spec/features/users/signup_spec.rb | 12 +- .../user_browses_projects_on_user_page_spec.rb | 2 +- spec/features/whats_new_spec.rb | 18 + 140 files changed, 4038 insertions(+), 2959 deletions(-) create mode 100644 spec/features/boards/sidebar_labels_in_namespaces_spec.rb delete mode 100644 spec/features/boards/sub_group_project_spec.rb create mode 100644 spec/features/boards/user_visits_board_spec.rb create mode 100644 spec/features/groups/milestones/gfm_autocomplete_spec.rb delete mode 100644 spec/features/issues/bulk_assignment_labels_spec.rb delete mode 100644 spec/features/issues/update_issues_spec.rb create mode 100644 spec/features/issues/user_bulk_edits_issues_labels_spec.rb create mode 100644 spec/features/issues/user_bulk_edits_issues_spec.rb create mode 100644 spec/features/monitor_sidebar_link_spec.rb delete mode 100644 spec/features/operations_sidebar_link_spec.rb delete mode 100644 spec/features/projects/files/gitlab_ci_syntax_yml_dropdown_spec.rb create mode 100644 spec/features/projects/integrations/user_activates_asana_spec.rb create mode 100644 spec/features/projects/integrations/user_activates_assembla_spec.rb create mode 100644 spec/features/projects/integrations/user_activates_atlassian_bamboo_ci_spec.rb create mode 100644 spec/features/projects/milestones/gfm_autocomplete_spec.rb create mode 100644 spec/features/projects/new_project_from_template_spec.rb delete mode 100644 spec/features/projects/services/user_activates_asana_spec.rb delete mode 100644 spec/features/projects/services/user_activates_assembla_spec.rb delete mode 100644 spec/features/projects/services/user_activates_atlassian_bamboo_ci_spec.rb create mode 100644 spec/features/projects/settings/monitor_settings_spec.rb delete mode 100644 spec/features/projects/settings/operations_settings_spec.rb (limited to 'spec/features') diff --git a/spec/features/action_cable_logging_spec.rb b/spec/features/action_cable_logging_spec.rb index ce7c0e03aad..2e6ce93f7f7 100644 --- a/spec/features/action_cable_logging_spec.rb +++ b/spec/features/action_cable_logging_spec.rb @@ -22,11 +22,7 @@ RSpec.describe 'ActionCable logging', :js do subscription_data = a_hash_including( remote_ip: '127.0.0.1', user_id: user.id, - username: user.username, - params: a_hash_including( - project_path: project.full_path, - iid: issue.iid.to_s - ) + username: user.username ) expect(ActiveSupport::Notifications).to receive(:instrument).with('subscribe.action_cable', subscription_data) diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb index 61e7efbc56c..603e757096f 100644 --- a/spec/features/admin/admin_appearance_spec.rb +++ b/spec/features/admin/admin_appearance_spec.rb @@ -37,7 +37,7 @@ RSpec.describe 'Admin Appearance' do expect_custom_sign_in_appearance(appearance) end - it 'preview new project page appearance' do + it 'preview new project page appearance', :js do sign_in(admin) gitlab_enable_admin_mode_sign_in(admin) @@ -86,10 +86,11 @@ RSpec.describe 'Admin Appearance' do expect_custom_sign_in_appearance(appearance) end - it 'custom new project page' do + it 'custom new project page', :js do sign_in(admin) gitlab_enable_admin_mode_sign_in(admin) visit new_project_path + find('[data-qa-selector="blank_project_link"]').click expect_custom_new_project_appearance(appearance) end diff --git a/spec/features/admin/admin_dev_ops_report_spec.rb b/spec/features/admin/admin_dev_ops_report_spec.rb index a05fa0640d8..33f984af807 100644 --- a/spec/features/admin/admin_dev_ops_report_spec.rb +++ b/spec/features/admin/admin_dev_ops_report_spec.rb @@ -53,15 +53,13 @@ RSpec.describe 'DevOps Report page', :js do end context 'when there is data to display' do - it 'shows numbers for each metric' do + it 'shows the DevOps Score app' do stub_application_setting(usage_ping_enabled: true) create(:dev_ops_report_metric) 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_selector('[data-testid="devops-score-app"]') end end end diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb index e7634f4e020..f9673a8aa2f 100644 --- a/spec/features/admin/admin_groups_spec.rb +++ b/spec/features/admin/admin_groups_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe 'Admin Groups' do include Select2Helper include Spec::Support::Helpers::Features::MembersHelpers + include Spec::Support::Helpers::Features::InviteMembersModalHelper let(:internal) { Gitlab::VisibilityLevel::INTERNAL } @@ -202,6 +203,7 @@ RSpec.describe 'Admin Groups' do select2(Gitlab::Access::REPORTER, from: '#access_level') end click_button "Add users to group" + page.within ".group-users-list" do expect(page).to have_content(user.name) expect(page).to have_content('Reporter') @@ -220,19 +222,13 @@ RSpec.describe 'Admin Groups' do describe 'add admin himself to a group' do before do - stub_feature_flags(invite_members_group_modal: false) group.add_user(:user, Gitlab::Access::OWNER) end it 'adds admin a to a group as developer', :js do visit group_group_members_path(group) - page.within '.invite-users-form' do - select2(current_user.id, from: '#user_ids', multiple: true) - select 'Developer', from: 'access_level' - end - - click_button 'Invite' + invite_member(current_user.name, role: 'Developer') page.within members_table do expect(page).to have_content(current_user.name) diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb index 43fb1f31a0f..08d81906d9f 100644 --- a/spec/features/admin/admin_labels_spec.rb +++ b/spec/features/admin/admin_labels_spec.rb @@ -36,7 +36,7 @@ RSpec.describe 'admin issues labels' do it 'deletes all labels', :js do page.within '.labels' do - page.all('.js-remove-row').each do |remove| + page.all('.js-remove-label').each do |remove| accept_confirm { remove.click } wait_for_requests end diff --git a/spec/features/admin/admin_mode/logout_spec.rb b/spec/features/admin/admin_mode/logout_spec.rb index 8cfac5d8b99..664eb51e58f 100644 --- a/spec/features/admin/admin_mode/logout_spec.rb +++ b/spec/features/admin/admin_mode/logout_spec.rb @@ -8,37 +8,67 @@ RSpec.describe 'Admin Mode Logout', :js do let(:user) { create(:admin) } - before do - stub_feature_flags(combined_menu: false) + shared_examples 'combined_menu: feature flag examples' do + before do + gitlab_sign_in(user) + gitlab_enable_admin_mode_sign_in(user) + visit admin_root_path + end - gitlab_sign_in(user) - gitlab_enable_admin_mode_sign_in(user) - visit admin_root_path - end + it 'disable removes admin mode and redirects to root page' do + pending_on_combined_menu_flag - it 'disable removes admin mode and redirects to root page' do - gitlab_disable_admin_mode + gitlab_disable_admin_mode - expect(current_path).to eq root_path - expect(page).to have_link(href: new_admin_session_path) - end + expect(current_path).to eq root_path + expect(page).to have_link(href: new_admin_session_path) + end + + it 'disable shows flash notice' do + pending_on_combined_menu_flag + + gitlab_disable_admin_mode + + expect(page).to have_selector('.flash-notice') + end - it 'disable shows flash notice' do - gitlab_disable_admin_mode + context 'on a read-only instance' do + before do + allow(Gitlab::Database).to receive(:read_only?).and_return(true) + end - expect(page).to have_selector('.flash-notice') + it 'disable removes admin mode and redirects to root page' do + pending_on_combined_menu_flag + + gitlab_disable_admin_mode + + expect(current_path).to eq root_path + expect(page).to have_link(href: new_admin_session_path) + end + end end - context 'on a read-only instance' do + context 'with combined_menu: feature flag on' do + let(:needs_rewrite_for_combined_menu_flag_on) { true } + before do - allow(Gitlab::Database).to receive(:read_only?).and_return(true) + stub_feature_flags(combined_menu: true) end - it 'disable removes admin mode and redirects to root page' do - gitlab_disable_admin_mode + it_behaves_like 'combined_menu: feature flag examples' + end - expect(current_path).to eq root_path - expect(page).to have_link(href: new_admin_session_path) + context 'with combined_menu feature flag off' do + let(:needs_rewrite_for_combined_menu_flag_on) { false } + + before do + stub_feature_flags(combined_menu: false) end + + it_behaves_like 'combined_menu: feature flag examples' + end + + def pending_on_combined_menu_flag + pending 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56587' if needs_rewrite_for_combined_menu_flag_on end end diff --git a/spec/features/admin/admin_mode_spec.rb b/spec/features/admin/admin_mode_spec.rb index 633de20c82d..4df035b13e8 100644 --- a/spec/features/admin/admin_mode_spec.rb +++ b/spec/features/admin/admin_mode_spec.rb @@ -8,55 +8,41 @@ RSpec.describe 'Admin mode' do let(:admin) { create(:admin) } - before do - stub_feature_flags(combined_menu: false) - - stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') - end - - context 'application setting :admin_mode is enabled', :request_store do + shared_examples 'combined_menu: feature flag examples' do before do - sign_in(admin) + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') end - context 'when not in admin mode' do - it 'has no leave admin mode button' do - visit new_admin_session_path - - page.within('.navbar-sub-nav') do - expect(page).not_to have_link(href: destroy_admin_session_path) - end + context 'application setting :admin_mode is enabled', :request_store do + before do + sign_in(admin) end - it 'can open pages not in admin scope' do - visit new_admin_session_path + context 'when not in admin mode' do + it 'has no leave admin mode button' do + visit new_admin_session_path - page.within('.navbar-sub-nav') do - find_all('a', text: 'Projects').first.click + page.within('.navbar-sub-nav') do + expect(page).not_to have_link(href: destroy_admin_session_path) + end end - expect(page).to have_current_path(dashboard_projects_path) - end - - it 'is necessary to provide credentials again before opening pages in admin scope' do - visit general_admin_application_settings_path # admin logged out because not in admin_mode - - expect(page).to have_current_path(new_admin_session_path) - end + it 'can open pages not in admin scope' do + pending_on_combined_menu_flag - it 'can enter admin mode' do - visit new_admin_session_path + visit new_admin_session_path - fill_in 'user_password', with: admin.password + page.within('.navbar-sub-nav') do + find_all('a', text: 'Projects').first.click + end - click_button 'Enter Admin Mode' + expect(page).to have_current_path(dashboard_projects_path) + end - expect(page).to have_current_path(admin_root_path) - end + it 'is necessary to provide credentials again before opening pages in admin scope' do + visit general_admin_application_settings_path # admin logged out because not in admin_mode - context 'on a read-only instance' do - before do - allow(Gitlab::Database).to receive(:read_only?).and_return(true) + expect(page).to have_current_path(new_admin_session_path) end it 'can enter admin mode' do @@ -68,108 +54,161 @@ RSpec.describe 'Admin mode' do expect(page).to have_current_path(admin_root_path) end - end - end - context 'when in admin_mode' do - before do - gitlab_enable_admin_mode_sign_in(admin) - end + context 'on a read-only instance' do + before do + allow(Gitlab::Database).to receive(:read_only?).and_return(true) + end - it 'contains link to leave admin mode' do - page.within('.navbar-sub-nav') do - expect(page).to have_link(href: destroy_admin_session_path) + it 'can enter admin mode' do + visit new_admin_session_path + + fill_in 'user_password', with: admin.password + + click_button 'Enter Admin Mode' + + expect(page).to have_current_path(admin_root_path) + end end end - it 'can leave admin mode using main dashboard link', :js do - page.within('.navbar-sub-nav') do - click_on 'Leave Admin Mode' + context 'when in admin_mode' do + before do + gitlab_enable_admin_mode_sign_in(admin) + end - expect(page).to have_link(href: new_admin_session_path) + it 'contains link to leave admin mode' do + pending_on_combined_menu_flag + + page.within('.navbar-sub-nav') do + expect(page).to have_link(href: destroy_admin_session_path) + end end - end - it 'can leave admin mode using dropdown menu on smaller screens', :js do - resize_screen_xs - visit root_dashboard_path + it 'can leave admin mode using main dashboard link', :js do + pending_on_combined_menu_flag - find('.header-more').click + page.within('.navbar-sub-nav') do + click_on 'Leave Admin Mode' - page.within '.navbar-sub-nav' do - click_on 'Leave Admin Mode' + expect(page).to have_link(href: new_admin_session_path) + end + end + + it 'can leave admin mode using dropdown menu on smaller screens', :js do + pending_on_combined_menu_flag + + resize_screen_xs + visit root_dashboard_path find('.header-more').click - expect(page).to have_link(href: new_admin_session_path) - end - end + page.within '.navbar-sub-nav' do + click_on 'Leave Admin Mode' - it 'can open pages not in admin scope' do - page.within('.navbar-sub-nav') do - find_all('a', text: 'Projects').first.click + find('.header-more').click - expect(page).to have_current_path(dashboard_projects_path) + expect(page).to have_link(href: new_admin_session_path) + end end - end - context 'nav bar' do - it 'shows admin dashboard links on bigger screen' do - visit root_dashboard_path + it 'can open pages not in admin scope' do + pending_on_combined_menu_flag - page.within '.navbar' do - expect(page).to have_link(text: 'Admin Area', href: admin_root_path, visible: true) - expect(page).to have_link(text: 'Leave Admin Mode', href: destroy_admin_session_path, visible: true) + page.within('.navbar-sub-nav') do + find_all('a', text: 'Projects').first.click + + expect(page).to have_current_path(dashboard_projects_path) end end - it 'relocates admin dashboard links to dropdown list on smaller screen', :js do - resize_screen_xs - visit root_dashboard_path + context 'nav bar' do + it 'shows admin dashboard links on bigger screen' do + pending_on_combined_menu_flag - page.within '.navbar' do - expect(page).not_to have_link(text: 'Admin Area', href: admin_root_path, visible: true) - expect(page).not_to have_link(text: 'Leave Admin Mode', href: destroy_admin_session_path, visible: true) + visit root_dashboard_path + + page.within '.navbar' do + expect(page).to have_link(text: 'Admin Area', href: admin_root_path, visible: true) + expect(page).to have_link(text: 'Leave Admin Mode', href: destroy_admin_session_path, visible: true) + end end - find('.header-more').click + it 'relocates admin dashboard links to dropdown list on smaller screen', :js do + pending_on_combined_menu_flag + + resize_screen_xs + visit root_dashboard_path - page.within '.navbar' do - expect(page).to have_link(text: 'Admin Area', href: admin_root_path, visible: true) - expect(page).to have_link(text: 'Leave Admin Mode', href: destroy_admin_session_path, visible: true) + page.within '.navbar' do + expect(page).not_to have_link(text: 'Leave Admin Mode', href: destroy_admin_session_path, visible: true) + end + + find('.header-more').click + + page.within '.navbar' do + expect(page).to have_link(text: 'Admin Area', href: admin_root_path, visible: true) + expect(page).to have_link(text: 'Leave Admin Mode', href: destroy_admin_session_path, visible: true) + end end end - end - context 'on a read-only instance' do - before do - allow(Gitlab::Database).to receive(:read_only?).and_return(true) - end + context 'on a read-only instance' do + before do + allow(Gitlab::Database).to receive(:read_only?).and_return(true) + end - it 'can leave admin mode', :js do - page.within('.navbar-sub-nav') do - click_on 'Leave Admin Mode' + it 'can leave admin mode', :js do + pending_on_combined_menu_flag - expect(page).to have_link(href: new_admin_session_path) + page.within('.navbar-sub-nav') do + click_on 'Leave Admin Mode' + + expect(page).to have_link(href: new_admin_session_path) + end end end end end + + context 'application setting :admin_mode is disabled' do + before do + stub_application_setting(admin_mode: false) + sign_in(admin) + end + + it 'shows no admin mode buttons in navbar' do + visit admin_root_path + + page.within('.navbar-sub-nav') do + expect(page).not_to have_link(href: new_admin_session_path) + expect(page).not_to have_link(href: destroy_admin_session_path) + end + end + end end - context 'application setting :admin_mode is disabled' do + context 'with combined_menu: feature flag on' do + let(:needs_rewrite_for_combined_menu_flag_on) { true } + before do - stub_application_setting(admin_mode: false) - sign_in(admin) + stub_feature_flags(combined_menu: true) end - it 'shows no admin mode buttons in navbar' do - visit admin_root_path + it_behaves_like 'combined_menu: feature flag examples' + end - page.within('.navbar-sub-nav') do - expect(page).not_to have_link(href: new_admin_session_path) - expect(page).not_to have_link(href: destroy_admin_session_path) - end + context 'with combined_menu feature flag off' do + let(:needs_rewrite_for_combined_menu_flag_on) { false } + + before do + stub_feature_flags(combined_menu: false) end + + it_behaves_like 'combined_menu: feature flag examples' + end + + def pending_on_combined_menu_flag + pending 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56587' if needs_rewrite_for_combined_menu_flag_on end end diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index bf280595ec7..cbbe9aa3b8b 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe "Admin::Projects" do include Spec::Support::Helpers::Features::MembersHelpers + include Spec::Support::Helpers::Features::InviteMembersModalHelper include Select2Helper let(:user) { create :user } @@ -95,21 +96,27 @@ RSpec.describe "Admin::Projects" do describe 'admin adds themselves to the project', :js do before do project.add_maintainer(user) - stub_feature_flags(invite_members_group_modal: false) end it 'adds admin to the project as developer' do visit project_project_members_path(project) - page.within '.invite-users-form' do - select2(current_user.id, from: '#user_ids', multiple: true) - select 'Developer', from: 'access_level' - end - - click_button 'Invite' + invite_member(current_user.name, role: 'Developer') expect(find_member_row(current_user)).to have_content('Developer') end + + context 'with the invite_members_group_modal feature flag disabled' do + it 'adds admin to the project as developer' do + stub_feature_flags(invite_members_group_modal: false) + + visit project_project_members_path(project) + + add_member_using_form(current_user.id, role: 'Developer') + + expect(find_member_row(current_user)).to have_content('Developer') + end + end end describe 'admin removes themselves from the project', :js do @@ -134,4 +141,19 @@ RSpec.describe "Admin::Projects" do expect(current_path).to match dashboard_projects_path end end + + # temporary method for the form until the :invite_members_group_modal feature flag is + # enabled: https://gitlab.com/gitlab-org/gitlab/-/issues/247208 + def add_member_using_form(id, role: 'Developer') + page.within '.invite-users-form' do + select2(id, from: '#user_ids', multiple: true) + + fill_in 'expires_at', with: 5.days.from_now.to_date + find_field('expires_at').native.send_keys :enter + + select(role, from: "access_level") + + click_on 'Invite' + end + end end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 90ce865cc00..0a7113a5559 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -251,40 +251,62 @@ RSpec.describe 'Admin updates settings' do end end - context 'when the Slack Notifications Service template is active' do + context 'when Service Templates are enabled' do before do - create(:service, :template, type: 'SlackService', active: true) - + stub_feature_flags(disable_service_templates: false) visit general_admin_application_settings_path end - it 'change Slack Notifications Service template settings', :js do - first(:link, 'Service Templates').click - click_link 'Slack notifications' - fill_in 'Webhook', with: 'http://localhost' - fill_in 'Username', with: 'test_user' - fill_in 'service[push_channel]', with: '#test_channel' - page.check('Notify only broken pipelines') - page.select 'All branches', from: 'Branches to be notified' + it 'shows Service Templates link' do + expect(page).to have_link('Service Templates') + end - check_all_events - click_button 'Save changes' + context 'when the Slack Notifications Service template is active' do + before do + create(:service, :template, type: 'SlackService', active: true) - expect(page).to have_content 'Application settings saved successfully' + visit general_admin_application_settings_path + end - click_link 'Slack notifications' + it 'change Slack Notifications Service template settings', :js do + first(:link, 'Service Templates').click + click_link 'Slack notifications' + fill_in 'Webhook', with: 'http://localhost' + fill_in 'Username', with: 'test_user' + fill_in 'service[push_channel]', with: '#test_channel' + page.check('Notify only broken pipelines') + page.select 'All branches', from: 'Branches to be notified' + page.select 'Match any of the labels', from: 'Labels to be notified behavior' + + check_all_events + click_button 'Save changes' + + expect(page).to have_content 'Application settings saved successfully' - expect(page.all('input[type=checkbox]')).to all(be_checked) - expect(find_field('Webhook').value).to eq 'http://localhost' - expect(find_field('Username').value).to eq 'test_user' - expect(find('[name="service[push_channel]"]').value).to eq '#test_channel' + click_link 'Slack notifications' + + expect(page.all('input[type=checkbox]')).to all(be_checked) + expect(find_field('Webhook').value).to eq 'http://localhost' + expect(find_field('Username').value).to eq 'test_user' + expect(find('[name="service[push_channel]"]').value).to eq '#test_channel' + end + + it 'defaults Deployment events to false for chat notification template settings', :js do + first(:link, 'Service Templates').click + click_link 'Slack notifications' + + expect(find_field('Deployment')).not_to be_checked + end end + end - it 'defaults Deployment events to false for chat notification template settings', :js do - first(:link, 'Service Templates').click - click_link 'Slack notifications' + context 'When Service templates are disabled' do + before do + stub_feature_flags(disable_service_templates: true) + end - expect(find_field('Deployment')).not_to be_checked + it 'does not show Service Templates link' do + expect(page).not_to have_link('Service Templates') end end @@ -424,7 +446,8 @@ RSpec.describe 'Admin updates settings' do check 'Enable reCAPTCHA for login' fill_in 'IPs per user', with: 15 check 'Enable Spam Check via external API endpoint' - fill_in 'URL of the external Spam Check endpoint', with: 'https://www.example.com/spamcheck' + fill_in 'URL of the external Spam Check endpoint', with: 'grpc://www.example.com/spamcheck' + fill_in 'Spam Check API Key', with: 'SPAM_CHECK_API_KEY' click_button 'Save changes' end @@ -433,7 +456,7 @@ RSpec.describe 'Admin updates settings' do expect(current_settings.login_recaptcha_protection_enabled).to be true expect(current_settings.unique_ips_limit_per_user).to eq(15) expect(current_settings.spam_check_endpoint_enabled).to be true - expect(current_settings.spam_check_endpoint_url).to eq 'https://www.example.com/spamcheck' + expect(current_settings.spam_check_endpoint_url).to eq 'grpc://www.example.com/spamcheck' end end diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index 4fc60d17886..6d5944002a1 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -10,61 +10,51 @@ RSpec.describe "Admin::Users" do gitlab_enable_admin_mode_sign_in(current_user) end - describe 'Tabs', :js do + describe 'Tabs' do let(:tabs_selector) { '.js-users-tabs' } let(:active_tab_selector) { '.nav-link.active' } - it 'does not add the tab param when the Users tab is selected' do - visit admin_users_path + it 'links to the Users tab' do + visit cohorts_admin_users_path within tabs_selector do click_link 'Users' + + expect(page).to have_selector active_tab_selector, text: 'Users' end expect(page).to have_current_path(admin_users_path) end - it 'adds the ?tab=cohorts param when the Cohorts tab is selected' do + it 'links to the Cohorts tab' do visit admin_users_path within tabs_selector do click_link 'Cohorts' + + expect(page).to have_selector active_tab_selector, text: 'Cohorts' end - expect(page).to have_current_path(admin_users_path(tab: 'cohorts')) + expect(page).to have_current_path(cohorts_admin_users_path) + expect(page).to have_selector active_tab_selector, text: 'Cohorts' end - it 'shows the cohorts tab when the tab param is set' do + it 'redirects legacy route' do visit admin_users_path(tab: 'cohorts') - within tabs_selector do - expect(page).to have_selector active_tab_selector, text: 'Cohorts' - end + expect(page).to have_current_path(cohorts_admin_users_path) end end describe 'Cohorts tab content' do - context 'with usage ping enabled' do - it 'shows users count per month' do - stub_application_setting(usage_ping_enabled: true) + it 'shows users count per month' do + stub_application_setting(usage_ping_enabled: false) - create_list(:user, 2) + create_list(:user, 2) - visit admin_users_path(tab: 'cohorts') - - expect(page).to have_content("#{Time.now.strftime('%b %Y')} 3 0") - end - end - - context 'with usage ping disabled' do - it 'shows empty state', :js do - stub_application_setting(usage_ping_enabled: false) - - visit admin_users_path(tab: 'cohorts') + visit admin_users_path(tab: 'cohorts') - expect(page).to have_selector(".js-empty-state") - expect(page).to have_content("Activate user activity analysis") - end + expect(page).to have_content("#{Time.now.strftime('%b %Y')} 3 0") end end end diff --git a/spec/features/admin/services/admin_visits_service_templates_spec.rb b/spec/features/admin/services/admin_visits_service_templates_spec.rb index 1fd8c8316e3..9d011b97f63 100644 --- a/spec/features/admin/services/admin_visits_service_templates_spec.rb +++ b/spec/features/admin/services/admin_visits_service_templates_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe 'Admin visits service templates' do let(:admin) { create(:user, :admin) } - let(:slack_service) { Service.for_template.find { |s| s.type == 'SlackService' } } + let(:slack_service) { Integration.for_template.find { |s| s.type == 'SlackService' } } before do sign_in(admin) diff --git a/spec/features/admin/users/user_spec.rb b/spec/features/admin/users/user_spec.rb index befa7bd338b..01341398135 100644 --- a/spec/features/admin/users/user_spec.rb +++ b/spec/features/admin/users/user_spec.rb @@ -4,18 +4,16 @@ require 'spec_helper' RSpec.describe 'Admin::Users::User' do let_it_be(:user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') } - let_it_be(:current_user) { create(:admin, last_activity_on: 5.days.ago) } + let_it_be(:current_user) { create(:admin) } before do sign_in(current_user) gitlab_enable_admin_mode_sign_in(current_user) - stub_feature_flags(vue_admin_users: false) end describe 'GET /admin/users/:id' do it 'has user info', :aggregate_failures do - visit admin_users_path - click_link user.name + visit admin_user_path(user) expect(page).to have_content(user.email) expect(page).to have_content(user.name) @@ -27,21 +25,6 @@ RSpec.describe 'Admin::Users::User' do expect(page).to have_button('Delete user and contributions') end - context 'user pending approval' do - it 'shows user info', :aggregate_failures do - user = create(:user, :blocked_pending_approval) - - visit admin_users_path - click_link 'Pending approval' - click_link user.name - - expect(page).to have_content(user.name) - expect(page).to have_content('Pending approval') - expect(page).to have_link('Approve user') - expect(page).to have_link('Reject request') - end - end - context 'when blocking/unblocking the user' do it 'shows confirmation and allows blocking and unblocking', :js do visit admin_user_path(user) @@ -171,6 +154,8 @@ RSpec.describe 'Admin::Users::User' do it 'logs in as the user when impersonate is clicked' do subject + find('[data-qa-selector="user_menu"]').click + expect(page.find(:css, '[data-testid="user-profile-link"]')['data-user']).to eql(another_user.username) end @@ -205,6 +190,8 @@ RSpec.describe 'Admin::Users::User' do it 'logs out of impersonated user back to original user' do subject + find('[data-qa-selector="user_menu"]').click + expect(page.find(:css, '[data-testid="user-profile-link"]')['data-user']).to eq(current_user.username) end @@ -238,6 +225,8 @@ RSpec.describe 'Admin::Users::User' do end it 'shows when disabled' do + user.update!(otp_required_for_login: false) + visit admin_user_path(user) expect_two_factor_status('Disabled') @@ -251,7 +240,7 @@ RSpec.describe 'Admin::Users::User' do end describe 'Email verification status' do - let!(:secondary_email) do + let_it_be(:secondary_email) do create :email, email: 'secondary@example.com', user: user end @@ -274,99 +263,121 @@ RSpec.describe 'Admin::Users::User' do expect(page).to have_content("#{secondary_email.email} Verified") end end - end - - describe 'show user attributes' do - it 'has expected attributes', :aggregate_failures do - visit admin_users_path - click_link user.name + describe 'show user identities' do + it 'shows user identities', :aggregate_failures do + visit admin_user_identities_path(user) - expect(page).to have_content 'Account' - expect(page).to have_content 'Personal projects limit' + expect(page).to have_content(user.name) + expect(page).to have_content('twitter') + end end - end - describe 'remove users secondary email', :js do - let!(:secondary_email) do - create :email, email: 'secondary@example.com', user: user + describe 'update user identities' do + before do + allow(Gitlab::Auth::OAuth::Provider).to receive(:providers).and_return([:twitter, :twitter_updated]) + end + + it 'modifies twitter identity', :aggregate_failures do + visit admin_user_identities_path(user) + + find('.table').find(:link, 'Edit').click + fill_in 'identity_extern_uid', with: '654321' + select 'twitter_updated', from: 'identity_provider' + click_button 'Save changes' + + expect(page).to have_content(user.name) + expect(page).to have_content('twitter_updated') + expect(page).to have_content('654321') + end end - it do - visit admin_user_path(user.username) + describe 'remove users secondary email', :js do + let_it_be(:secondary_email) do + create :email, email: 'secondary@example.com', user: user + end + + it do + visit admin_user_path(user.username) - expect(page).to have_content("Secondary email: #{secondary_email.email}") + expect(page).to have_content("Secondary email: #{secondary_email.email}") - accept_confirm { find("#remove_email_#{secondary_email.id}").click } + accept_confirm { find("#remove_email_#{secondary_email.id}").click } - expect(page).not_to have_content(secondary_email.email) + expect(page).not_to have_content(secondary_email.email) + end end - end - describe 'show user keys', :js do - it do - key1 = create(:key, user: user, title: 'ssh-rsa Key1', key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4FIEBXGi4bPU8kzxMefudPIJ08/gNprdNTaO9BR/ndy3+58s2HCTw2xCHcsuBmq+TsAqgEidVq4skpqoTMB+Uot5Uzp9z4764rc48dZiI661izoREoKnuRQSsRqUTHg5wrLzwxlQbl1MVfRWQpqiz/5KjBC7yLEb9AbusjnWBk8wvC1bQPQ1uLAauEA7d836tgaIsym9BrLsMVnR4P1boWD3Xp1B1T/ImJwAGHvRmP/ycIqmKdSpMdJXwxcb40efWVj0Ibbe7ii9eeoLdHACqevUZi6fwfbymdow+FeqlkPoHyGg3Cu4vD/D8+8cRc7mE/zGCWcQ15Var83Tczour Key1') - key2 = create(:key, user: user, title: 'ssh-rsa Key2', key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2') + describe 'remove user with identities' do + it 'removes user with twitter identity', :aggregate_failures do + visit admin_user_identities_path(user) - visit admin_users_path + click_link 'Delete' - click_link user.name - click_link 'SSH keys' + expect(page).to have_content(user.name) + expect(page).not_to have_content('twitter') + end + end - expect(page).to have_content(key1.title) - expect(page).to have_content(key2.title) + describe 'show user keys', :js do + it do + key1 = create(:key, user: user, title: 'ssh-rsa Key1', key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4FIEBXGi4bPU8kzxMefudPIJ08/gNprdNTaO9BR/ndy3+58s2HCTw2xCHcsuBmq+TsAqgEidVq4skpqoTMB+Uot5Uzp9z4764rc48dZiI661izoREoKnuRQSsRqUTHg5wrLzwxlQbl1MVfRWQpqiz/5KjBC7yLEb9AbusjnWBk8wvC1bQPQ1uLAauEA7d836tgaIsym9BrLsMVnR4P1boWD3Xp1B1T/ImJwAGHvRmP/ycIqmKdSpMdJXwxcb40efWVj0Ibbe7ii9eeoLdHACqevUZi6fwfbymdow+FeqlkPoHyGg3Cu4vD/D8+8cRc7mE/zGCWcQ15Var83Tczour Key1') + key2 = create(:key, user: user, title: 'ssh-rsa Key2', key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2') - click_link key2.title + visit admin_user_path(user) - expect(page).to have_content(key2.title) - expect(page).to have_content(key2.key) + click_link 'SSH keys' - click_button 'Delete' + expect(page).to have_content(key1.title) + expect(page).to have_content(key2.title) - page.within('.modal') do - page.click_button('Delete') - end + click_link key2.title - expect(page).not_to have_content(key2.title) - end - end + expect(page).to have_content(key2.title) + expect(page).to have_content(key2.key) - describe 'show user identities' do - it 'shows user identities', :aggregate_failures do - visit admin_user_identities_path(user) + click_button 'Delete' - expect(page).to have_content(user.name) - expect(page).to have_content('twitter') - end - end + page.within('.modal') do + page.click_button('Delete') + end - describe 'update user identities' do - before do - allow(Gitlab::Auth::OAuth::Provider).to receive(:providers).and_return([:twitter, :twitter_updated]) + expect(page).not_to have_content(key2.title) + end end - it 'modifies twitter identity', :aggregate_failures do - visit admin_user_identities_path(user) - - find('.table').find(:link, 'Edit').click - fill_in 'identity_extern_uid', with: '654321' - select 'twitter_updated', from: 'identity_provider' - click_button 'Save changes' + describe 'show user attributes' do + it 'has expected attributes', :aggregate_failures do + visit admin_user_path(user) - expect(page).to have_content(user.name) - expect(page).to have_content('twitter_updated') - expect(page).to have_content('654321') + expect(page).to have_content 'Account' + expect(page).to have_content 'Personal projects limit' + end end end - describe 'remove user with identities' do - it 'removes user with twitter identity', :aggregate_failures do - visit admin_user_identities_path(user) + [true, false].each do |vue_admin_users| + context "with vue_admin_users feature flag set to #{vue_admin_users}", js: vue_admin_users do + before do + stub_feature_flags(vue_admin_users: vue_admin_users) + end - click_link 'Delete' + describe 'GET /admin/users' do + context 'user pending approval' do + it 'shows user info', :aggregate_failures do + user = create(:user, :blocked_pending_approval) - expect(page).to have_content(user.name) - expect(page).not_to have_content('twitter') + visit admin_users_path + click_link 'Pending approval' + click_link user.name + + expect(page).to have_content(user.name) + expect(page).to have_content('Pending approval') + expect(page).to have_link('Approve user') + expect(page).to have_link('Reject request') + end + end + end end end end diff --git a/spec/features/admin/users/users_spec.rb b/spec/features/admin/users/users_spec.rb index 9482b4f8603..d3931373ee3 100644 --- a/spec/features/admin/users/users_spec.rb +++ b/spec/features/admin/users/users_spec.rb @@ -3,298 +3,306 @@ require 'spec_helper' RSpec.describe 'Admin::Users' do - include Spec::Support::Helpers::Features::ResponsiveTableHelpers - let_it_be(:user, reload: true) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') } - let_it_be(:current_user) { create(:admin, last_activity_on: 5.days.ago) } + let_it_be(:current_user) { create(:admin) } before do sign_in(current_user) gitlab_enable_admin_mode_sign_in(current_user) end - describe 'GET /admin/users' do - before do - stub_feature_flags(vue_admin_users: false) - visit admin_users_path - end + [true, false].each do |vue_admin_users| + context "with vue_admin_users feature flag set to #{vue_admin_users}", js: vue_admin_users do + before do + stub_feature_flags(vue_admin_users: vue_admin_users) + end - it "is ok" do - expect(current_path).to eq(admin_users_path) - end + describe 'GET /admin/users' do + before do + visit admin_users_path + end - it "has users list" do - expect(page).to have_content(current_user.email) - expect(page).to have_content(current_user.name) - expect(page).to have_content(current_user.created_at.strftime('%e %b, %Y')) - expect(page).to have_content(current_user.last_activity_on.strftime('%e %b, %Y')) - expect(page).to have_content(user.email) - expect(page).to have_content(user.name) - expect(page).to have_content('Projects') - expect(page).to have_button('Block') - expect(page).to have_button('Deactivate') - expect(page).to have_button('Delete user') - expect(page).to have_button('Delete user and contributions') - end + it "is ok" do + expect(current_path).to eq(admin_users_path) + end - describe 'view extra user information' do - it 'shows the user popover on hover', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/11290' do - expect(page).not_to have_selector('#__BV_popover_1__') + it "has users list" do + current_user.reload - first_user_link = page.first('.js-user-link') - first_user_link.hover + expect(page).to have_content(current_user.email) + expect(page).to have_content(current_user.name) + expect(page).to have_content(current_user.created_at.strftime('%e %b, %Y')) + expect(page).to have_content(user.email) + expect(page).to have_content(user.name) + expect(page).to have_content('Projects') - expect(page).to have_selector('#__BV_popover_1__') - end - end + click_user_dropdown_toggle(user.id) - context 'user project count' do - before do - project = create(:project) - project.add_maintainer(current_user) - end + expect(page).to have_button('Block') + expect(page).to have_button('Deactivate') + expect(page).to have_button('Delete user') + expect(page).to have_button('Delete user and contributions') + end - it 'displays count of users projects' do - visit admin_users_path + it 'clicking edit user takes us to edit page', :aggregate_failures do + page.within("[data-testid='user-actions-#{user.id}']") do + click_link 'Edit' + end - expect(page.find("[data-testid='user-project-count-#{current_user.id}']").text).to eq("1") - end - end + expect(page).to have_content('Name') + expect(page).to have_content('Password') + end - describe 'tabs' do - it 'has multiple tabs to filter users' do - expect(page).to have_link('Active', href: admin_users_path) - expect(page).to have_link('Admins', href: admin_users_path(filter: 'admins')) - expect(page).to have_link('2FA Enabled', href: admin_users_path(filter: 'two_factor_enabled')) - expect(page).to have_link('2FA Disabled', href: admin_users_path(filter: 'two_factor_disabled')) - expect(page).to have_link('External', href: admin_users_path(filter: 'external')) - expect(page).to have_link('Blocked', href: admin_users_path(filter: 'blocked')) - expect(page).to have_link('Deactivated', href: admin_users_path(filter: 'deactivated')) - expect(page).to have_link('Without projects', href: admin_users_path(filter: 'wop')) - end + describe 'view extra user information' do + it 'shows the user popover on hover', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/11290' do + expect(page).not_to have_selector('#__BV_popover_1__') - context '`Pending approval` tab' do - before do - visit admin_users_path - end + first_user_link = page.first('.js-user-link') + first_user_link.hover - it 'shows the `Pending approval` tab' do - expect(page).to have_link('Pending approval', href: admin_users_path(filter: 'blocked_pending_approval')) + expect(page).to have_selector('#__BV_popover_1__') + end end - end - end - describe 'search and sort' do - before_all do - create(:user, name: 'Foo Bar', last_activity_on: 3.days.ago) - create(:user, name: 'Foo Baz', last_activity_on: 2.days.ago) - create(:user, name: 'Dmitriy') - end + context 'user project count' do + before do + project = create(:project) + project.add_maintainer(current_user) + end - it 'searches users by name' do - visit admin_users_path(search_query: 'Foo') + it 'displays count of users projects' do + visit admin_users_path - expect(page).to have_content('Foo Bar') - expect(page).to have_content('Foo Baz') - expect(page).not_to have_content('Dmitriy') - end + expect(page.find("[data-testid='user-project-count-#{current_user.id}']").text).to eq("1") + end + end - it 'sorts users by name' do - visit admin_users_path + describe 'tabs' do + it 'has multiple tabs to filter users' do + expect(page).to have_link('Active', href: admin_users_path) + expect(page).to have_link('Admins', href: admin_users_path(filter: 'admins')) + expect(page).to have_link('2FA Enabled', href: admin_users_path(filter: 'two_factor_enabled')) + expect(page).to have_link('2FA Disabled', href: admin_users_path(filter: 'two_factor_disabled')) + expect(page).to have_link('External', href: admin_users_path(filter: 'external')) + expect(page).to have_link('Blocked', href: admin_users_path(filter: 'blocked')) + expect(page).to have_link('Banned', href: admin_users_path(filter: 'banned')) + expect(page).to have_link('Deactivated', href: admin_users_path(filter: 'deactivated')) + expect(page).to have_link('Without projects', href: admin_users_path(filter: 'wop')) + end + + context '`Pending approval` tab' do + before do + 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 + end + end - sort_by('Name') + describe 'search and sort' do + before_all do + create(:user, name: 'Foo Bar', last_activity_on: 3.days.ago) + create(:user, name: 'Foo Baz', last_activity_on: 2.days.ago) + create(:user, name: 'Dmitriy') + end - expect(first_row.text).to include('Dmitriy') - expect(second_row.text).to include('Foo Bar') - end + it 'searches users by name' do + visit admin_users_path(search_query: 'Foo') - it 'sorts search results only' do - visit admin_users_path(search_query: 'Foo') + expect(page).to have_content('Foo Bar') + expect(page).to have_content('Foo Baz') + expect(page).not_to have_content('Dmitriy') + end - sort_by('Name') + it 'sorts users by name' do + visit admin_users_path - expect(page).not_to have_content('Dmitriy') - expect(first_row.text).to include('Foo Bar') - expect(second_row.text).to include('Foo Baz') - end + sort_by('Name') - it 'searches with respect of sorting' do - visit admin_users_path(sort: 'Name') + expect(first_row.text).to include('Dmitriy') + expect(second_row.text).to include('Foo Bar') + end - fill_in :search_query, with: 'Foo' - click_button('Search users') + it 'sorts search results only' do + visit admin_users_path(search_query: 'Foo') - expect(first_row.text).to include('Foo Bar') - expect(second_row.text).to include('Foo Baz') - end + sort_by('Name') + expect(page).not_to have_content('Dmitriy') + expect(first_row.text).to include('Foo Bar') + expect(second_row.text).to include('Foo Baz') + end - it 'sorts users by recent last activity' do - visit admin_users_path(search_query: 'Foo') + it 'searches with respect of sorting' do + visit admin_users_path(sort: 'Name') - sort_by('Recent last activity') + fill_in :search_query, with: 'Foo' + click_button('Search users') - expect(first_row.text).to include('Foo Baz') - expect(second_row.text).to include('Foo Bar') - end + expect(first_row.text).to include('Foo Bar') + expect(second_row.text).to include('Foo Baz') + end - it 'sorts users by oldest last activity' do - visit admin_users_path(search_query: 'Foo') + it 'sorts users by recent last activity' do + visit admin_users_path(search_query: 'Foo') - sort_by('Oldest last activity') + sort_by('Recent last activity') - expect(first_row.text).to include('Foo Bar') - expect(second_row.text).to include('Foo Baz') - end - end + expect(first_row.text).to include('Foo Baz') + expect(second_row.text).to include('Foo Bar') + end - describe 'Two-factor Authentication filters' do - it 'counts users who have enabled 2FA' do - create(:user, :two_factor) + it 'sorts users by oldest last activity' do + visit admin_users_path(search_query: 'Foo') - visit admin_users_path + sort_by('Oldest last activity') - page.within('.filter-two-factor-enabled small') do - expect(page).to have_content('1') + expect(first_row.text).to include('Foo Bar') + expect(second_row.text).to include('Foo Baz') + end end - end - it 'filters by users who have enabled 2FA' do - user = create(:user, :two_factor) + describe 'Two-factor Authentication filters' do + it 'counts users who have enabled 2FA' do + create(:user, :two_factor) - visit admin_users_path - click_link '2FA Enabled' + visit admin_users_path - expect(page).to have_content(user.email) - end + page.within('.filter-two-factor-enabled small') do + expect(page).to have_content('1') + end + end - it 'counts users who have not enabled 2FA' do - visit admin_users_path + it 'filters by users who have enabled 2FA' do + user = create(:user, :two_factor) - page.within('.filter-two-factor-disabled small') do - expect(page).to have_content('2') # Including admin - end - end + visit admin_users_path + click_link '2FA Enabled' - it 'filters by users who have not enabled 2FA' do - visit admin_users_path - click_link '2FA Disabled' + expect(page).to have_content(user.email) + end - expect(page).to have_content(user.email) - end - end + it 'counts users who have not enabled 2FA' do + visit admin_users_path - describe 'Pending approval filter' do - it 'counts users who are pending approval' do - create_list(:user, 2, :blocked_pending_approval) + page.within('.filter-two-factor-disabled small') do + expect(page).to have_content('2') # Including admin + end + end - visit admin_users_path + it 'filters by users who have not enabled 2FA' do + visit admin_users_path + click_link '2FA Disabled' - page.within('.filter-blocked-pending-approval small') do - expect(page).to have_content('2') + expect(page).to have_content(user.email) + end end - end - it 'filters by users who are pending approval' do - user = create(:user, :blocked_pending_approval) + describe 'Pending approval filter' do + it 'counts users who are pending approval' do + create_list(:user, 2, :blocked_pending_approval) - visit admin_users_path - click_link 'Pending approval' + visit admin_users_path - expect(page).to have_content(user.email) - end - end + page.within('.filter-blocked-pending-approval small') do + expect(page).to have_content('2') + end + end - context 'when blocking/unblocking a user' do - it 'shows confirmation and allows blocking and unblocking', :js do - expect(page).to have_content(user.email) + it 'filters by users who are pending approval' do + user = create(:user, :blocked_pending_approval) - click_action_in_user_dropdown(user.id, 'Block') + visit admin_users_path + click_link 'Pending approval' - wait_for_requests + expect(page).to have_content(user.email) + end + end - 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') + context 'when blocking/unblocking a user' do + it 'shows confirmation and allows blocking and unblocking', :js do + expect(page).to have_content(user.email) - find('.modal-footer button', text: 'Block').click + click_action_in_user_dropdown(user.id, 'Block') - wait_for_requests + wait_for_requests - expect(page).to have_content('Successfully blocked') - expect(page).not_to have_content(user.email) + 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') - click_link 'Blocked' + find('.modal-footer button', text: 'Block').click - wait_for_requests + wait_for_requests - expect(page).to have_content(user.email) + expect(page).to have_content('Successfully blocked') + expect(page).not_to have_content(user.email) - click_action_in_user_dropdown(user.id, 'Unblock') + click_link 'Blocked' - expect(page).to have_content('Unblock user') - expect(page).to have_content('You can always block their account again if needed.') + wait_for_requests - find('.modal-footer button', text: 'Unblock').click + expect(page).to have_content(user.email) - wait_for_requests + click_action_in_user_dropdown(user.id, 'Unblock') - expect(page).to have_content('Successfully unblocked') - expect(page).not_to have_content(user.email) - end - end + expect(page).to have_content('Unblock user') + expect(page).to have_content('You can always block their account again if needed.') - context 'when deactivating/re-activating a user' do - it 'shows confirmation and allows deactivating and re-activating', :js do - expect(page).to have_content(user.email) + find('.modal-footer button', text: 'Unblock').click - click_action_in_user_dropdown(user.id, 'Deactivate') + wait_for_requests - expect(page).to have_content('Deactivate user') - expect(page).to have_content('Deactivating a user has the following effects') - expect(page).to have_content('The user will be logged out') - expect(page).to have_content('Personal projects, group and user history will be left intact') + expect(page).to have_content('Successfully unblocked') + expect(page).not_to have_content(user.email) + end + end - find('.modal-footer button', text: 'Deactivate').click + context 'when deactivating/re-activating a user' do + it 'shows confirmation and allows deactivating and re-activating', :js do + expect(page).to have_content(user.email) - wait_for_requests + click_action_in_user_dropdown(user.id, 'Deactivate') - expect(page).to have_content('Successfully deactivated') - expect(page).not_to have_content(user.email) + expect(page).to have_content('Deactivate user') + expect(page).to have_content('Deactivating a user has the following effects') + expect(page).to have_content('The user will be logged out') + expect(page).to have_content('Personal projects, group and user history will be left intact') - click_link 'Deactivated' + find('.modal-footer button', text: 'Deactivate').click - wait_for_requests + wait_for_requests - expect(page).to have_content(user.email) + expect(page).to have_content('Successfully deactivated') + expect(page).not_to have_content(user.email) - click_action_in_user_dropdown(user.id, 'Activate') + click_link 'Deactivated' - expect(page).to have_content('Activate user') - expect(page).to have_content('You can always deactivate their account again if needed.') + wait_for_requests - find('.modal-footer button', text: 'Activate').click + expect(page).to have_content(user.email) - wait_for_requests + click_action_in_user_dropdown(user.id, 'Activate') - expect(page).to have_content('Successfully activated') - expect(page).not_to have_content(user.email) - end - end + expect(page).to have_content('Activate user') + expect(page).to have_content('You can always deactivate their account again if needed.') - def click_action_in_user_dropdown(user_id, action) - find("[data-testid='user-action-button-#{user_id}']").click + find('.modal-footer button', text: 'Activate').click - within find("[data-testid='user-action-dropdown-#{user_id}']") do - find('li button', text: action).click - end + wait_for_requests - wait_for_requests + expect(page).to have_content('Successfully activated') + expect(page).not_to have_content(user.email) + end + end + end end end describe 'GET /admin/users/new' do - let(:user_username) { 'bang' } + let_it_be(:user_username) { 'bang' } before do visit new_admin_user_path @@ -344,7 +352,7 @@ RSpec.describe 'Admin::Users' do end context 'username contains spaces' do - let(:user_username) { 'Bing bang' } + let_it_be(:user_username) { 'Bing bang' } it "doesn't create the user and shows an error message" do expect { click_button 'Create user' }.to change {User.count}.by(0) @@ -363,22 +371,6 @@ RSpec.describe 'Admin::Users' do visit new_admin_user_path end - def expects_external_to_be_checked - expect(find('#user_external')).to be_checked - end - - def expects_external_to_be_unchecked - expect(find('#user_external')).not_to be_checked - end - - def expects_warning_to_be_hidden - expect(find('#warning_external_automatically_set', visible: :all)[:class]).to include 'hidden' - end - - def expects_warning_to_be_shown - expect(find('#warning_external_automatically_set')[:class]).not_to include 'hidden' - end - it 'automatically unchecks external for matching email' do expects_external_to_be_checked expects_warning_to_be_hidden @@ -413,55 +405,22 @@ RSpec.describe 'Admin::Users' do expect(new_user.external).to be_falsy end - end - end - end - - describe 'GET /admin/users/:id/edit' do - before do - stub_feature_flags(vue_admin_users: false) - visit admin_users_path - click_link "edit_user_#{user.id}" - end - - it 'has user edit page' do - expect(page).to have_content('Name') - expect(page).to have_content('Password') - end - - describe 'Update user' do - before do - fill_in 'user_name', with: 'Big Bang' - fill_in 'user_email', with: 'bigbang@mail.com' - fill_in 'user_password', with: 'AValidPassword1' - fill_in 'user_password_confirmation', with: 'AValidPassword1' - choose 'user_access_level_admin' - click_button 'Save changes' - end - - it 'shows page with new data' do - expect(page).to have_content('bigbang@mail.com') - expect(page).to have_content('Big Bang') - end - it 'changes user entry' do - user.reload - expect(user.name).to eq('Big Bang') - expect(user.admin?).to be_truthy - expect(user.password_expires_at).to be <= Time.now - end - end + def expects_external_to_be_checked + expect(find('#user_external')).to be_checked + end - describe 'update username to non ascii char' do - it do - fill_in 'user_username', with: '\u3042\u3044' - click_button('Save') + def expects_external_to_be_unchecked + expect(find('#user_external')).not_to be_checked + end - page.within '#error_explanation' do - expect(page).to have_content('Username') + def expects_warning_to_be_hidden + expect(find('#warning_external_automatically_set', visible: :all)[:class]).to include 'hidden' end - expect(page).to have_selector(%(form[action="/admin/users/#{user.username}"])) + def expects_warning_to_be_shown + expect(find('#warning_external_automatically_set')[:class]).not_to include 'hidden' + end end end end @@ -541,15 +500,108 @@ RSpec.describe 'Admin::Users' do check_breadcrumb('Edit Identity') end + + def check_breadcrumb(content) + expect(find('.breadcrumbs-sub-title')).to have_content(content) + end end - def check_breadcrumb(content) - expect(find('.breadcrumbs-sub-title')).to have_content(content) + describe 'GET /admin/users/:id/edit' do + before do + visit edit_admin_user_path(user) + end + + describe 'Update user' do + before do + fill_in 'user_name', with: 'Big Bang' + fill_in 'user_email', with: 'bigbang@mail.com' + fill_in 'user_password', with: 'AValidPassword1' + fill_in 'user_password_confirmation', with: 'AValidPassword1' + choose 'user_access_level_admin' + click_button 'Save changes' + end + + it 'shows page with new data' do + expect(page).to have_content('bigbang@mail.com') + expect(page).to have_content('Big Bang') + end + + it 'changes user entry' do + user.reload + expect(user.name).to eq('Big Bang') + expect(user.admin?).to be_truthy + expect(user.password_expires_at).to be <= Time.now + end + end + + describe 'update username to non ascii char' do + it do + fill_in 'user_username', with: '\u3042\u3044' + click_button('Save') + + page.within '#error_explanation' do + expect(page).to have_content('Username') + end + + expect(page).to have_selector(%(form[action="/admin/users/#{user.username}"])) + end + end + end + + # TODO: Move to main GET /admin/users block once feature flag is removed. Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/290737 + context 'with vue_admin_users feature flag enabled', :js do + before do + stub_feature_flags(vue_admin_users: true) + end + + describe 'GET /admin/users' do + context 'user group count', :js do + before do + group = create(:group) + group.add_developer(current_user) + project = create(:project, group: create(:group)) + project.add_reporter(current_user) + end + + it 'displays count of the users authorized groups' do + visit admin_users_path + + wait_for_requests + + expect(page.find("[data-testid='user-group-count-#{current_user.id}']").text).to eq("2") + end + end + end end - def sort_by(text) - page.within('.user-sort-dropdown') do - click_link text + def click_user_dropdown_toggle(user_id) + page.within("[data-testid='user-actions-#{user_id}']") do + find("[data-testid='dropdown-toggle']").click end end + + def first_row + page.all('[role="row"]')[1] + end + + def second_row + page.all('[role="row"]')[2] + end + + def sort_by(option) + page.within('.filtered-search-block') do + find('.dropdown-menu-toggle').click + click_link option + end + end + + def click_action_in_user_dropdown(user_id, action) + click_user_dropdown_toggle(user_id) + + within find("[data-testid='user-actions-#{user_id}']") do + find('li button', text: action).click + end + + wait_for_requests + end end diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index ab544022bff..5d9bb8d8087 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -119,46 +119,21 @@ RSpec.describe 'Project issue boards', :js do end context 'search list negation queries' do - context 'with the NOT queries feature flag disabled' do - before do - stub_feature_flags(not_issuable_queries: false) - - visit_project_board_path_without_query_limit(project, board) - end - - it 'does not have the != option' do - find('.filtered-search').set('label:') - - wait_for_requests - within('#js-dropdown-operator') do - tokens = all(:css, 'li.filter-dropdown-item') - expect(tokens.count).to eq(1) - button = tokens[0].find('button') - expect(button).to have_content('=') - expect(button).not_to have_content('!=') - end - end + before do + visit_project_board_path_without_query_limit(project, board) end - context 'with the NOT queries feature flag enabled' do - before do - stub_feature_flags(not_issuable_queries: true) - - visit_project_board_path_without_query_limit(project, board) - end - - it 'does not have the != option' do - find('.filtered-search').set('label:') + it 'does not have the != option' do + find('.filtered-search').set('label:') - wait_for_requests - within('#js-dropdown-operator') do - tokens = all(:css, 'li.filter-dropdown-item') - expect(tokens.count).to eq(2) - button = tokens[0].find('button') - expect(button).to have_content('=') - button = tokens[1].find('button') - expect(button).to have_content('!=') - end + wait_for_requests + within('#js-dropdown-operator') do + tokens = all(:css, 'li.filter-dropdown-item') + expect(tokens.count).to eq(2) + button = tokens[0].find('button') + expect(button).to have_content('=') + button = tokens[1].find('button') + expect(button).to have_content('!=') end end end diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index 20ae569322c..129d03d17f3 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -10,6 +10,9 @@ RSpec.describe 'Issue Boards new issue', :js do let_it_be(:list) { create(:list, board: board, label: label, position: 0) } let_it_be(:user) { create(:user) } + let(:board_list_header) { first('[data-testid="board-list-header"]') } + let(:project_select_dropdown) { find('[data-testid="project-select-dropdown"]') } + context 'authorized user' do before do project.add_maintainer(user) @@ -24,18 +27,18 @@ RSpec.describe 'Issue Boards new issue', :js do end it 'displays new issue button' do - expect(first('.board')).to have_selector('.issue-count-badge-add-button', count: 1) + expect(first('.board')).to have_button('New issue', count: 1) end it 'does not display new issue button in closed list' do page.within('.board:nth-child(3)') do - expect(page).not_to have_selector('.issue-count-badge-add-button') + expect(page).not_to have_button('New issue') end end it 'shows form when clicking button' do page.within(first('.board')) do - find('.issue-count-badge-add-button').click + click_button 'New issue' expect(page).to have_selector('.board-new-issue-form') end @@ -43,7 +46,7 @@ RSpec.describe 'Issue Boards new issue', :js do it 'hides form when clicking cancel' do page.within(first('.board')) do - find('.issue-count-badge-add-button').click + click_button 'New issue' expect(page).to have_selector('.board-new-issue-form') @@ -55,7 +58,7 @@ RSpec.describe 'Issue Boards new issue', :js do it 'creates new issue' do page.within(first('.board')) do - find('.issue-count-badge-add-button').click + click_button 'New issue' end page.within(first('.board-new-issue-form')) do @@ -80,7 +83,7 @@ RSpec.describe 'Issue Boards new issue', :js do # TODO https://gitlab.com/gitlab-org/gitlab/-/issues/323446 xit 'shows sidebar when creating new issue' do page.within(first('.board')) do - find('.issue-count-badge-add-button').click + click_button 'New issue' end page.within(first('.board-new-issue-form')) do @@ -95,7 +98,7 @@ RSpec.describe 'Issue Boards new issue', :js do it 'successfuly loads labels to be added to newly created issue' do page.within(first('.board')) do - find('.issue-count-badge-add-button').click + click_button 'New issue' end page.within(first('.board-new-issue-form')) do @@ -109,12 +112,12 @@ RSpec.describe 'Issue Boards new issue', :js do find('.board-card').click end - page.within(first('[data-testid="issue-boards-sidebar"]')) do - find('.labels [data-testid="edit-button"]').click + page.within('[data-testid="sidebar-labels"]') do + click_button 'Edit' wait_for_requests - expect(page).to have_selector('.labels-select-contents-list .dropdown-content li a') + expect(page).to have_content 'Label 1' end end end @@ -126,70 +129,94 @@ RSpec.describe 'Issue Boards new issue', :js do end it 'displays new issue button in open list' do - expect(first('.board')).to have_selector('.issue-count-badge-add-button', count: 1) + expect(first('.board')).to have_button('New issue', count: 1) end it 'does not display new issue button in label list' do page.within('.board:nth-child(2)') do - expect(page).not_to have_selector('.issue-count-badge-add-button') + expect(page).not_to have_button('New issue') end end end context 'group boards' do let_it_be(:group) { create(:group, :public) } - let_it_be(:project) { create(:project, :public, namespace: group) } + let_it_be(:project) { create(:project, namespace: group, name: "root project") } + let_it_be(:subgroup) { create(:group, parent: group) } + let_it_be(:subproject1) { create(:project, group: subgroup, name: "sub project1") } + let_it_be(:subproject2) { create(:project, group: subgroup, name: "sub project2") } let_it_be(:group_board) { create(:board, group: group) } let_it_be(:project_label) { create(:label, project: project, name: 'label') } let_it_be(:list) { create(:list, board: group_board, label: project_label, position: 0) } context 'for unauthorized users' do - context 'when backlog does not exist' do - before do - sign_in(user) - visit group_board_path(group, group_board) - wait_for_requests - end + before do + visit group_board_path(group, group_board) + wait_for_requests + end + context 'when backlog does not exist' do it 'does not display new issue button in label list' do page.within('.board.is-draggable') do - expect(page).not_to have_selector('.issue-count-badge-add-button') + expect(page).not_to have_button('New issue') end end end context 'when backlog list already exists' do - let!(:backlog_list) { create(:backlog_list, board: group_board) } - - before do - sign_in(user) - visit group_board_path(group, group_board) - wait_for_requests - end + let_it_be(:backlog_list) { create(:backlog_list, board: group_board) } it 'displays new issue button in open list' do - expect(first('.board')).to have_selector('.issue-count-badge-add-button', count: 1) + expect(first('.board')).to have_button('New issue', count: 1) end it 'does not display new issue button in label list' do page.within('.board.is-draggable') do - expect(page).not_to have_selector('.issue-count-badge-add-button') + expect(page).not_to have_button('New issue') end end end end context 'for authorized users' do - it 'display new issue button in label list' do - project = create(:project, namespace: group) + before do project.add_reporter(user) + subproject1.add_reporter(user) sign_in(user) visit group_board_path(group, group_board) wait_for_requests + end + + context 'when backlog does not exist' do + it 'display new issue button in label list' do + expect(board_list_header).to have_button('New issue') + end + end + + context 'project select dropdown' do + let_it_be(:backlog_list) { create(:backlog_list, board: group_board) } + + before do + page.within(board_list_header) do + click_button 'New issue' + end + + project_select_dropdown.click + + wait_for_requests + end + + it 'lists a project which is a direct descendant of the top-level group' do + expect(project_select_dropdown).to have_button("root project") + end + + it 'lists a project that belongs to a subgroup' do + expect(project_select_dropdown).to have_button("sub project1") + end - page.within('.board.is-draggable') do - expect(page).to have_selector('.issue-count-badge-add-button') + it "does not list projects to which user doesn't have access" do + expect(project_select_dropdown).not_to have_button("sub project2") end end end diff --git a/spec/features/boards/sidebar_assignee_spec.rb b/spec/features/boards/sidebar_assignee_spec.rb index e938612163f..d6adefea6e3 100644 --- a/spec/features/boards/sidebar_assignee_spec.rb +++ b/spec/features/boards/sidebar_assignee_spec.rb @@ -18,6 +18,8 @@ RSpec.describe 'Project issue boards sidebar assignee', :js do let(:card) { find('.board:nth-child(2)').first('.board-card') } before do + stub_licensed_features(multiple_issue_assignees: false) + project.add_maintainer(user) sign_in(user) @@ -27,10 +29,12 @@ RSpec.describe 'Project issue boards sidebar assignee', :js do end context 'assignee' do + let(:assignees_widget) { '[data-testid="issue-boards-sidebar"] [data-testid="assignees-widget"]' } + it 'updates the issues assignee' do click_card(card) - page.within('.assignee') do + page.within(assignees_widget) do click_button('Edit') wait_for_requests @@ -41,12 +45,11 @@ RSpec.describe 'Project issue boards sidebar assignee', :js do first('.gl-avatar-labeled').click end - click_button('Apply') - wait_for_requests - expect(page).to have_content(assignee) end + wait_for_requests + expect(card).to have_selector('.avatar') end @@ -54,7 +57,7 @@ RSpec.describe 'Project issue boards sidebar assignee', :js do card_two = find('.board:nth-child(2)').find('.board-card:nth-child(2)') click_card(card_two) - page.within('.assignee') do + page.within(assignees_widget) do click_button('Edit') wait_for_requests @@ -63,9 +66,6 @@ RSpec.describe 'Project issue boards sidebar assignee', :js do find('[data-testid="unassign"]').click end - click_button('Apply') - wait_for_requests - expect(page).to have_content('None') end @@ -75,7 +75,7 @@ RSpec.describe 'Project issue boards sidebar assignee', :js do it 'assignees to current user' do click_card(card) - page.within(find('.assignee')) do + page.within(assignees_widget) do expect(page).to have_content('None') click_button 'assign yourself' @@ -91,7 +91,7 @@ RSpec.describe 'Project issue boards sidebar assignee', :js do it 'updates assignee dropdown' do click_card(card) - page.within('.assignee') do + page.within(assignees_widget) do click_button('Edit') wait_for_requests @@ -102,9 +102,6 @@ RSpec.describe 'Project issue boards sidebar assignee', :js do first('.gl-avatar-labeled').click end - click_button('Apply') - wait_for_requests - expect(page).to have_content(assignee) end @@ -112,7 +109,7 @@ RSpec.describe 'Project issue boards sidebar assignee', :js do find('.board-card:nth-child(2)').click end - page.within('.assignee') do + page.within(assignees_widget) do click_button('Edit') expect(find('.dropdown-menu')).to have_selector('.gl-new-dropdown-item-check-icon') diff --git a/spec/features/boards/sidebar_labels_in_namespaces_spec.rb b/spec/features/boards/sidebar_labels_in_namespaces_spec.rb new file mode 100644 index 00000000000..8395a0b33c0 --- /dev/null +++ b/spec/features/boards/sidebar_labels_in_namespaces_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Issue boards sidebar labels select', :js do + include BoardHelpers + + include_context 'labels from nested groups and projects' + + let(:card) { find('.board:nth-child(1)').first('[data-testid="board_card"]') } + + context 'group boards' do + context 'in the top-level group board' do + let_it_be(:group_board) { create(:board, group: group) } + let_it_be(:board_list) { create(:backlog_list, board: group_board) } + + before do + load_board group_board_path(group, group_board) + end + + context 'selecting an issue from a direct descendant project' do + let_it_be(:project_issue) { create(:issue, project: project) } + + include_examples 'an issue from a direct descendant project is selected' + end + + context "selecting an issue from a subgroup's project" do + let_it_be(:subproject_issue) { create(:issue, project: subproject) } + + include_examples "an issue from a subgroup's project is selected" + end + end + end +end diff --git a/spec/features/boards/sub_group_project_spec.rb b/spec/features/boards/sub_group_project_spec.rb deleted file mode 100644 index bde5f061a67..00000000000 --- a/spec/features/boards/sub_group_project_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Sub-group project issue boards', :js do - let(:group) { create(:group) } - let(:nested_group_1) { create(:group, parent: group) } - let(:project) { create(:project, group: nested_group_1) } - let(:board) { create(:board, project: project) } - let(:label) { create(:label, project: project) } - let(:user) { create(:user) } - let!(:list1) { create(:list, board: board, label: label, position: 0) } - let!(:issue) { create(:labeled_issue, project: project, labels: [label]) } - - before do - project.add_maintainer(user) - - sign_in(user) - - visit project_board_path(project, board) - wait_for_requests - end - - # TODO https://gitlab.com/gitlab-org/gitlab/-/issues/324290 - xit 'creates new label from sidebar' do - find('.board-card').click - - page.within '.labels' do - click_link 'Edit' - click_link 'Create project label' - end - - page.within '.dropdown-new-label' do - fill_in 'new_label_name', with: 'test label' - first('.suggest-colors-dropdown a').click - - click_button 'Create' - - wait_for_requests - end - - page.within '.labels' do - expect(page).to have_link 'test label' - end - end -end diff --git a/spec/features/boards/user_visits_board_spec.rb b/spec/features/boards/user_visits_board_spec.rb new file mode 100644 index 00000000000..7fe32557d6a --- /dev/null +++ b/spec/features/boards/user_visits_board_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User visits issue boards', :js do + using RSpec::Parameterized::TableSyntax + + let_it_be(:group) { create_default(:group, :public) } + let_it_be(:project) { create_default(:project, :public, group: group) } + + # TODO use 'let' when rspec-parameterized supports it. + # https://gitlab.com/gitlab-org/gitlab/-/issues/329746 + label_name1 = 'foobar' + label_name2 = 'in dev' + assignee_username = 'root' + issue_with_label1 = "issue with label1" + issue_with_label2 = "issue with label2" + issue_with_assignee = "issue with assignee" + issue_with_milestone = "issue with milestone" + issue_with_all_filters = "issue with all filters" + + let_it_be(:label1) { create(:group_label, group: group, name: label_name1) } + let_it_be(:label2) { create(:group_label, group: group, name: label_name2) } + let_it_be(:assignee) { create_default(:group_member, :maintainer, user: create(:user, username: assignee_username), group: group ).user } + let_it_be(:milestone) { create_default(:milestone, project: project, start_date: Date.today - 1, due_date: 7.days.from_now) } + + before_all do + create_default(:issue, project: project, title: issue_with_label1, labels: [label1]) + create_default(:issue, project: project, title: issue_with_label2, labels: [label2]) + create_default(:issue, project: project, title: issue_with_assignee, assignees: [assignee]) + create_default(:issue, project: project, title: issue_with_milestone, milestone: milestone) + create_default(:issue, project: project, title: issue_with_all_filters, labels: [label1, label2], assignees: [assignee], milestone: milestone) + end + + shared_examples "visiting board path with search params" do + where(:params, :expected_issues) do + { "label_name" => [label_name1] } | [issue_with_label1, issue_with_all_filters] + { "label_name" => [label_name2] } | [issue_with_label2, issue_with_all_filters] + { "label_name" => [label_name1, label_name2] } | [issue_with_all_filters] + { "assignee_username" => assignee_username } | [issue_with_assignee, issue_with_all_filters] + { "milestone_title" => '#started' } | [issue_with_milestone, issue_with_all_filters] + { "label_name" => [label_name1, label_name2], "assignee_username" => assignee_username } | [issue_with_all_filters] + end + + with_them do + before do + visit board_path + + wait_for_requests + end + + it 'displays all issues satisfiying filter params and correctly sets url params' do + expect(page).to have_current_path(board_path) + + page.assert_selector('[data-testid="board_card"]', count: expected_issues.length) + expected_issues.each { |issue_title| expect(page).to have_link issue_title } + end + end + end + + context "project boards" do + let_it_be(:board) { create_default(:board, project: project) } + let_it_be(:backlog_list) { create_default(:backlog_list, board: board) } + + let(:board_path) { project_boards_path(project, params) } + + include_examples "visiting board path with search params" + end + + context "group boards" do + let_it_be(:board) { create_default(:board, group: group) } + let_it_be(:backlog_list) { create_default(:backlog_list, board: board) } + + let(:board_path) { group_boards_path(group, params) } + + include_examples 'visiting board path with search params' + end +end diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb index 0b73226268d..1281d890ef7 100644 --- a/spec/features/calendar_spec.rb +++ b/spec/features/calendar_spec.rb @@ -146,7 +146,7 @@ RSpec.describe 'Contributions Calendar', :js do describe '1 issue creation calendar activity' do before do - Issues::CreateService.new(contributed_project, user, issue_params).execute + Issues::CreateService.new(project: contributed_project, current_user: user, params: issue_params).execute end it_behaves_like 'a day with activity', contribution_count: 1 @@ -181,7 +181,7 @@ RSpec.describe 'Contributions Calendar', :js do push_code_contribution travel_to(Date.yesterday) do - Issues::CreateService.new(contributed_project, user, issue_params).execute + Issues::CreateService.new(project: contributed_project, current_user: user, params: issue_params).execute end end include_context 'visit user page' diff --git a/spec/features/dashboard/active_tab_spec.rb b/spec/features/dashboard/active_tab_spec.rb index 3a532cb4161..a1fb0beda70 100644 --- a/spec/features/dashboard/active_tab_spec.rb +++ b/spec/features/dashboard/active_tab_spec.rb @@ -3,30 +3,56 @@ require 'spec_helper' RSpec.describe 'Dashboard Active Tab', :js do - before do - stub_feature_flags(combined_menu: false) + shared_examples 'combined_menu: feature flag examples' do + before do + sign_in(create(:user)) + end - sign_in(create(:user)) - end + shared_examples 'page has active tab' do |title| + it "#{title} tab" do + pending_on_combined_menu_flag + + subject - shared_examples 'page has active tab' do |title| - it "#{title} tab" do - subject + expect(page).to have_selector('.navbar-sub-nav li.active', count: 1) + expect(find('.navbar-sub-nav li.active')).to have_content(title) + end + end + + context 'on dashboard projects' do + it_behaves_like 'page has active tab', 'Projects' do + subject { visit dashboard_projects_path } + end + end - expect(page).to have_selector('.navbar-sub-nav li.active', count: 1) - expect(find('.navbar-sub-nav li.active')).to have_content(title) + context 'on dashboard groups' do + it_behaves_like 'page has active tab', 'Groups' do + subject { visit dashboard_groups_path } + end end end - context 'on dashboard projects' do - it_behaves_like 'page has active tab', 'Projects' do - subject { visit dashboard_projects_path } + context 'with combined_menu: feature flag on' do + let(:needs_rewrite_for_combined_menu_flag_on) { true } + + before do + stub_feature_flags(combined_menu: true) end + + it_behaves_like 'combined_menu: feature flag examples' end - context 'on dashboard groups' do - it_behaves_like 'page has active tab', 'Groups' do - subject { visit dashboard_groups_path } + context 'with combined_menu feature flag off' do + let(:needs_rewrite_for_combined_menu_flag_on) { false } + + before do + stub_feature_flags(combined_menu: false) end + + it_behaves_like 'combined_menu: feature flag examples' + end + + def pending_on_combined_menu_flag + pending 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56587' if needs_rewrite_for_combined_menu_flag_on end end diff --git a/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb b/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb index 179d9d09905..0620f819332 100644 --- a/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb +++ b/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb @@ -7,36 +7,64 @@ RSpec.describe 'The group dashboard' do let(:user) { create(:user) } - before do - stub_feature_flags(combined_menu: false) + shared_examples 'combined_menu: feature flag examples' do + before do + sign_in user + end - sign_in user - end + describe 'The top navigation' do + it 'has all the expected links' do + pending_on_combined_menu_flag - describe 'The top navigation' do - it 'has all the expected links' do - visit dashboard_groups_path + visit dashboard_groups_path - within('.navbar') do - expect(page).to have_button('Projects') - expect(page).to have_button('Groups') - expect(page).to have_link('Activity') - expect(page).to have_link('Milestones') - expect(page).to have_link('Snippets') + within('.navbar') do + expect(page).to have_button('Projects') + expect(page).to have_button('Groups') + expect(page).to have_link('Activity') + expect(page).to have_link('Milestones') + expect(page).to have_link('Snippets') + end end - end - it 'hides some links when an external authorization service is enabled' do - enable_external_authorization_service_check - visit dashboard_groups_path + it 'hides some links when an external authorization service is enabled' do + pending_on_combined_menu_flag + + enable_external_authorization_service_check + visit dashboard_groups_path - within('.navbar') do - expect(page).to have_button('Projects') - expect(page).to have_button('Groups') - expect(page).not_to have_link('Activity') - expect(page).not_to have_link('Milestones') - expect(page).to have_link('Snippets') + within('.navbar') do + expect(page).to have_button('Projects') + expect(page).to have_button('Groups') + expect(page).not_to have_link('Activity') + expect(page).not_to have_link('Milestones') + expect(page).to have_link('Snippets') + end end end end + + context 'with combined_menu: feature flag on' do + let(:needs_rewrite_for_combined_menu_flag_on) { true } + + before do + stub_feature_flags(combined_menu: true) + end + + it_behaves_like 'combined_menu: feature flag examples' + end + + context 'with combined_menu feature flag off' do + let(:needs_rewrite_for_combined_menu_flag_on) { false } + + before do + stub_feature_flags(combined_menu: false) + end + + it_behaves_like 'combined_menu: feature flag examples' + end + + def pending_on_combined_menu_flag + pending 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56587' if needs_rewrite_for_combined_menu_flag_on + end end diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb index e96a60b2ab2..5f60832dbc9 100644 --- a/spec/features/dashboard/shortcuts_spec.rb +++ b/spec/features/dashboard/shortcuts_spec.rb @@ -3,71 +3,97 @@ require 'spec_helper' RSpec.describe 'Dashboard shortcuts', :js do - before do - stub_feature_flags(combined_menu: false) - end + shared_examples 'combined_menu: feature flag examples' do + context 'logged in' do + let(:user) { create(:user) } + let(:project) { create(:project) } - context 'logged in' do - let(:user) { create(:user) } - let(:project) { create(:project) } + before do + project.add_developer(user) + sign_in(user) + visit root_dashboard_path + end - before do - project.add_developer(user) - sign_in(user) - visit root_dashboard_path - end + it 'navigate to tabs' do + pending_on_combined_menu_flag - it 'navigate to tabs' do - find('body').send_keys([:shift, 'I']) + find('body').send_keys([:shift, 'I']) - check_page_title('Issues') + check_page_title('Issues') - find('body').send_keys([:shift, 'M']) + find('body').send_keys([:shift, 'M']) - check_page_title('Merge requests') + check_page_title('Merge requests') - find('body').send_keys([:shift, 'T']) + find('body').send_keys([:shift, 'T']) - check_page_title('To-Do List') + check_page_title('To-Do List') - find('body').send_keys([:shift, 'G']) + find('body').send_keys([:shift, 'G']) - check_page_title('Groups') + check_page_title('Groups') - find('body').send_keys([:shift, 'P']) + find('body').send_keys([:shift, 'P']) - check_page_title('Projects') + check_page_title('Projects') - find('body').send_keys([:shift, 'A']) + find('body').send_keys([:shift, 'A']) - check_page_title('Activity') + check_page_title('Activity') + end end - end - context 'logged out' do - before do - visit explore_root_path + context 'logged out' do + before do + visit explore_root_path + end + + it 'navigate to tabs' do + pending_on_combined_menu_flag + + find('body').send_keys([:shift, 'G']) + + find('.nothing-here-block') + expect(page).to have_content('No public groups') + + find('body').send_keys([:shift, 'S']) + + find('.nothing-here-block') + expect(page).to have_content('No snippets found') + + find('body').send_keys([:shift, 'P']) + + find('.nothing-here-block') + expect(page).to have_content('Explore public groups to find projects to contribute to.') + end end - it 'navigate to tabs' do - find('body').send_keys([:shift, 'G']) + def check_page_title(title) + expect(find('.page-title')).to have_content(title) + end + end - find('.nothing-here-block') - expect(page).to have_content('No public groups') + context 'with combined_menu: feature flag on' do + let(:needs_rewrite_for_combined_menu_flag_on) { true } - find('body').send_keys([:shift, 'S']) + before do + stub_feature_flags(combined_menu: true) + end - find('.nothing-here-block') - expect(page).to have_content('No snippets found') + it_behaves_like 'combined_menu: feature flag examples' + end - find('body').send_keys([:shift, 'P']) + context 'with combined_menu feature flag off' do + let(:needs_rewrite_for_combined_menu_flag_on) { false } - find('.nothing-here-block') - expect(page).to have_content('Explore public groups to find projects to contribute to.') + before do + stub_feature_flags(combined_menu: false) end + + it_behaves_like 'combined_menu: feature flag examples' end - def check_page_title(title) - expect(find('.page-title')).to have_content(title) + def pending_on_combined_menu_flag + pending 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56587' if needs_rewrite_for_combined_menu_flag_on end end diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb index 0b4fed55f11..0bc6cc9c017 100644 --- a/spec/features/dashboard/todos/todos_spec.rb +++ b/spec/features/dashboard/todos/todos_spec.rb @@ -79,7 +79,7 @@ RSpec.describe 'Dashboard Todos' do end it 'has not "All done" message' do - expect(page).not_to have_selector('.todos-all-done') + expect(page).not_to have_selector('.empty-state') end end diff --git a/spec/features/frequently_visited_projects_and_groups_spec.rb b/spec/features/frequently_visited_projects_and_groups_spec.rb index 6c25afdf6d4..9110c7ad65a 100644 --- a/spec/features/frequently_visited_projects_and_groups_spec.rb +++ b/spec/features/frequently_visited_projects_and_groups_spec.rb @@ -5,45 +5,73 @@ require 'spec_helper' RSpec.describe 'Frequently visited items', :js do let_it_be(:user) { create(:user) } - before do - stub_feature_flags(combined_menu: false) + shared_examples 'combined_menu: feature flag examples' do + before do + sign_in(user) + end - sign_in(user) - end + context 'for projects' do + let_it_be(:project) { create(:project, :public) } - context 'for projects' do - let_it_be(:project) { create(:project, :public) } + it 'increments localStorage counter when visiting the project' do + pending_on_combined_menu_flag - it 'increments localStorage counter when visiting the project' do - visit project_path(project) + visit project_path(project) - frequent_projects = nil + frequent_projects = nil - wait_for('localStorage frequent-projects') do - frequent_projects = page.evaluate_script("localStorage['#{user.username}/frequent-projects']") + wait_for('localStorage frequent-projects') do + frequent_projects = page.evaluate_script("localStorage['#{user.username}/frequent-projects']") - frequent_projects.present? - end + frequent_projects.present? + end - expect(Gitlab::Json.parse(frequent_projects)).to contain_exactly(a_hash_including('id' => project.id, 'frequency' => 1)) + expect(Gitlab::Json.parse(frequent_projects)).to contain_exactly(a_hash_including('id' => project.id, 'frequency' => 1)) + end end - end - context 'for groups' do - let_it_be(:group) { create(:group, :public) } + context 'for groups' do + let_it_be(:group) { create(:group, :public) } - it 'increments localStorage counter when visiting the group' do - visit group_path(group) + it 'increments localStorage counter when visiting the group' do + pending_on_combined_menu_flag - frequent_groups = nil + visit group_path(group) - wait_for('localStorage frequent-groups') do - frequent_groups = page.evaluate_script("localStorage['#{user.username}/frequent-groups']") + frequent_groups = nil - frequent_groups.present? + 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 - expect(Gitlab::Json.parse(frequent_groups)).to contain_exactly(a_hash_including('id' => group.id, 'frequency' => 1)) + context 'with combined_menu: feature flag on' do + let(:needs_rewrite_for_combined_menu_flag_on) { true } + + before do + stub_feature_flags(combined_menu: true) end + + it_behaves_like 'combined_menu: feature flag examples' + end + + context 'with combined_menu feature flag off' do + let(:needs_rewrite_for_combined_menu_flag_on) { false } + + before do + stub_feature_flags(combined_menu: false) + end + + it_behaves_like 'combined_menu: feature flag examples' + end + + def pending_on_combined_menu_flag + pending 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56587' if needs_rewrite_for_combined_menu_flag_on end end diff --git a/spec/features/groups/group_page_with_external_authorization_service_spec.rb b/spec/features/groups/group_page_with_external_authorization_service_spec.rb index 187d878472e..59a7feb813b 100644 --- a/spec/features/groups/group_page_with_external_authorization_service_spec.rb +++ b/spec/features/groups/group_page_with_external_authorization_service_spec.rb @@ -15,8 +15,7 @@ RSpec.describe 'The group page' do def expect_all_sidebar_links within('.nav-sidebar') do - expect(page).to have_link('Group overview') - expect(page).to have_link('Details') + expect(page).to have_link('Group information') expect(page).to have_link('Activity') expect(page).to have_link('Issues') expect(page).to have_link('Merge requests') @@ -44,8 +43,7 @@ RSpec.describe 'The group page' do visit group_path(group) within('.nav-sidebar') do - expect(page).to have_link('Group overview') - expect(page).to have_link('Details') + expect(page).to have_link('Group information') expect(page).not_to have_link('Activity') expect(page).not_to have_link('Contribution') diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb index b0d2f90145f..21b39d2da46 100644 --- a/spec/features/groups/issues_spec.rb +++ b/spec/features/groups/issues_spec.rb @@ -217,7 +217,7 @@ RSpec.describe 'Group issues page' do it 'first pagination item is active' do page.within('.gl-pagination') do - expect(find('.active')).to have_content('1') + expect(find('li.active')).to have_content('1') end end end diff --git a/spec/features/groups/members/manage_groups_spec.rb b/spec/features/groups/members/manage_groups_spec.rb index e9bbe9de3c9..40cd54c1e33 100644 --- a/spec/features/groups/members/manage_groups_spec.rb +++ b/spec/features/groups/members/manage_groups_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe 'Groups > Members > Manage groups', :js do include Select2Helper include Spec::Support::Helpers::Features::MembersHelpers + include Spec::Support::Helpers::Features::InviteMembersModalHelper let_it_be(:user) { create(:user) } @@ -12,18 +13,43 @@ RSpec.describe 'Groups > Members > Manage groups', :js do sign_in(user) end - context 'when group link does not exist' do - let_it_be(:group) { create(:group) } - let_it_be(:group_to_add) { create(:group) } - + context 'with invite_members_group_modal disabled' do before do stub_feature_flags(invite_members_group_modal: false) - group.add_owner(user) - visit group_group_members_path(group) end - it 'add group to group' do - add_group(group_to_add.id, 'Reporter') + context 'when group link does not exist' do + let_it_be(:group) { create(:group) } + let_it_be(:group_to_add) { create(:group) } + + before do + group.add_owner(user) + group_to_add.add_owner(user) + visit group_group_members_path(group) + end + + it 'can share group with group' do + add_group(group_to_add.id, 'Reporter') + + click_groups_tab + + page.within(first_row) do + expect(page).to have_content(group_to_add.name) + expect(page).to have_content('Reporter') + end + end + end + end + + context 'when group link does not exist' do + it 'can share a group with group' do + group = create(:group) + group_to_add = create(:group) + group.add_owner(user) + group_to_add.add_owner(user) + + visit group_group_members_path(group) + invite_group(group_to_add.name, role: 'Reporter') click_groups_tab diff --git a/spec/features/groups/members/manage_members_spec.rb b/spec/features/groups/members/manage_members_spec.rb index 3b637a10abe..c5e6479ec51 100644 --- a/spec/features/groups/members/manage_members_spec.rb +++ b/spec/features/groups/members/manage_members_spec.rb @@ -5,13 +5,13 @@ require 'spec_helper' RSpec.describe 'Groups > Members > Manage members' do include Select2Helper include Spec::Support::Helpers::Features::MembersHelpers + include Spec::Support::Helpers::Features::InviteMembersModalHelper let(:user1) { create(:user, name: 'John Doe') } let(:user2) { create(:user, name: 'Mary Jane') } let(:group) { create(:group) } before do - stub_feature_flags(invite_members_group_modal: false) sign_in(user1) end @@ -26,16 +26,28 @@ RSpec.describe 'Groups > Members > Manage members' do end end - context 'when Invite Members modal is enabled' do - before do - stub_feature_flags(invite_members_group_modal: true) + shared_examples 'does not include either invite modal or either invite form' do + it 'does not include either of the invite members or invite group modal buttons' do + expect(page).not_to have_selector '.js-invite-members-modal' + expect(page).not_to have_selector '.js-invite-group-modal' end + it 'does not include either of the invite users or invite group forms' do + expect(page).not_to have_selector '.invite-users-form' + expect(page).not_to have_selector '.invite-group-form' + end + end + + context 'when Invite Members modal is enabled' do it_behaves_like 'includes the correct Invite link', '.js-invite-members-trigger', '.invite-users-form' it_behaves_like 'includes the correct Invite link', '.js-invite-group-trigger', '.invite-group-form' end context 'when Invite Members modal is disabled' do + before do + stub_feature_flags(invite_members_group_modal: false) + end + it_behaves_like 'includes the correct Invite link', '.invite-users-form', '.js-invite-members-trigger' it_behaves_like 'includes the correct Invite link', '.invite-group-form', '.js-invite-group-trigger' end @@ -59,7 +71,7 @@ RSpec.describe 'Groups > Members > Manage members' do visit group_group_members_path(group) - add_user(user2.id, 'Reporter') + invite_member(user2.name, role: 'Reporter') page.within(second_row) do expect(page).to have_content(user2.name) @@ -73,21 +85,46 @@ RSpec.describe 'Groups > Members > Manage members' do visit group_group_members_path(group) - find('.select2-container').click - select_input = find('.select2-input') + click_on 'Invite members' + fill_in 'Select members or type email addresses', with: '@gitlab.com' - select_input.send_keys('@gitlab.com') wait_for_requests expect(page).to have_content('No matches found') - select_input.native.clear - select_input.send_keys('undisclosed_email@gitlab.com') + fill_in 'Select members or type email addresses', with: 'undisclosed_email@gitlab.com' wait_for_requests expect(page).to have_content("Jane 'invisible' Doe") end + context 'when Invite Members modal is disabled' do + before do + stub_feature_flags(invite_members_group_modal: false) + end + + it 'do not disclose email addresses', :js do + group.add_owner(user1) + create(:user, email: 'undisclosed_email@gitlab.com', name: "Jane 'invisible' Doe") + + visit group_group_members_path(group) + + find('.select2-container').click + select_input = find('.select2-input') + + select_input.send_keys('@gitlab.com') + wait_for_requests + + expect(page).to have_content('No matches found') + + select_input.native.clear + select_input.send_keys('undisclosed_email@gitlab.com') + wait_for_requests + + expect(page).to have_content("Jane 'invisible' Doe") + end + end + it 'remove user from group', :js do group.add_owner(user1) group.add_developer(user2) @@ -115,7 +152,7 @@ RSpec.describe 'Groups > Members > Manage members' do visit group_group_members_path(group) - add_user(user1.id, 'Reporter') + invite_member(user1.name, role: 'Reporter') page.within(first_row) do expect(page).to have_content(user1.name) @@ -128,7 +165,7 @@ RSpec.describe 'Groups > Members > Manage members' do visit group_group_members_path(group) - add_user('test@example.com', 'Reporter') + invite_member('test@example.com', role: 'Reporter') expect(page).to have_link 'Invited' click_link 'Invited' @@ -140,29 +177,46 @@ RSpec.describe 'Groups > Members > Manage members' do end end - it 'guest can not manage other users', :js do - group.add_guest(user1) - group.add_developer(user2) + context 'as a guest', :js do + before do + group.add_guest(user1) + group.add_developer(user2) - visit group_group_members_path(group) + visit group_group_members_path(group) + end - expect(page).not_to have_selector '.invite-users-form' - expect(page).not_to have_selector '.invite-group-form' + it_behaves_like 'does not include either invite modal or either invite form' - page.within(second_row) do - # Can not modify user2 role - expect(page).not_to have_button 'Developer' + it 'does not include a button on the members page list to manage or remove the existing member', :js do + page.within(second_row) do + # Can not modify user2 role + expect(page).not_to have_button 'Developer' - # Can not remove user2 - expect(page).not_to have_selector 'button[title="Remove member"]' + # Can not remove user2 + expect(page).not_to have_selector 'button[title="Remove member"]' + end end end - def add_user(id, role) - page.within ".invite-users-form" do - select2(id, from: "#user_ids", multiple: true) - select(role, from: "access_level") - click_button "Invite" + context 'As a guest when the :invite_members_group_modal feature flag is disabled', :js do + before do + stub_feature_flags(invite_members_group_modal: false) + group.add_guest(user1) + group.add_developer(user2) + + visit group_group_members_path(group) + end + + it_behaves_like 'does not include either invite modal or either invite form' + + it 'does not include a button on the members page list to manage or remove the existing member', :js do + page.within(second_row) do + # Can not modify user2 role + expect(page).not_to have_button 'Developer' + + # Can not remove user2 + expect(page).not_to have_selector 'button[title="Remove member"]' + end end 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 d31a7977f66..ddf3c6d8f9b 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 @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js do - include Select2Helper include Spec::Support::Helpers::Features::MembersHelpers + include Spec::Support::Helpers::Features::InviteMembersModalHelper let_it_be(:user1) { create(:user, name: 'John Doe') } let_it_be(:group) { create(:group) } @@ -12,7 +12,6 @@ RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js let(:new_member) { create(:user, name: 'Mary Jane') } before do - stub_feature_flags(invite_members_group_modal: false) group.add_owner(user1) sign_in(user1) end @@ -20,14 +19,7 @@ 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 - select2(new_member.id, from: '#user_ids', multiple: true) - - fill_in 'expires_at', with: 5.days.from_now.to_date - find_field('expires_at').native.send_keys :enter - - click_on 'Invite' - end + invite_member(new_member.name, role: 'Guest', expires_at: 5.days.from_now.to_date) page.within second_row do expect(page).to have_content(/in \d days/) diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb index 1d9ac5ee1e9..c51ee250331 100644 --- a/spec/features/groups/milestone_spec.rb +++ b/spec/features/groups/milestone_spec.rb @@ -54,11 +54,11 @@ RSpec.describe 'Group milestones' do expect(find('.start_date')).to have_content(Date.today.at_beginning_of_month.strftime('%b %-d, %Y')) end - it 'description input does not support autocomplete' do + it 'description input support autocomplete' do description = find('.note-textarea') description.native.send_keys('!') - expect(page).not_to have_selector('.atwho-view') + expect(page).to have_selector('.atwho-view') end end diff --git a/spec/features/groups/milestones/gfm_autocomplete_spec.rb b/spec/features/groups/milestones/gfm_autocomplete_spec.rb new file mode 100644 index 00000000000..85a14123294 --- /dev/null +++ b/spec/features/groups/milestones/gfm_autocomplete_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'GFM autocomplete', :js do + let_it_be(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') } + let_it_be(:group) { create(:group, name: 'Ancestor') } + let_it_be(:project) { create(:project, :repository, group: group) } + let_it_be(:issue) { create(:issue, project: project, assignees: [user], title: 'My special issue') } + let_it_be(:label) { create(:group_label, group: group, title: 'special+') } + let_it_be(:milestone) { create(:milestone, resource_parent: group, title: "group milestone") } + let_it_be(:merge_request) { create(:merge_request, source_project: project) } + + shared_examples 'displays autocomplete menu for all entities' do + it 'autocompletes all available entities' do + fill_in 'Description', with: User.reference_prefix + wait_for_requests + expect(find_autocomplete_menu).to be_visible + expect_autocomplete_entry(group.name) + + fill_in 'Description', with: Label.reference_prefix + wait_for_requests + expect(find_autocomplete_menu).to be_visible + expect_autocomplete_entry(label.title) + + fill_in 'Description', with: Milestone.reference_prefix + wait_for_requests + expect(find_autocomplete_menu).to be_visible + expect_autocomplete_entry(milestone.title) + + fill_in 'Description', with: Issue.reference_prefix + wait_for_requests + expect(find_autocomplete_menu).to be_visible + expect_autocomplete_entry(issue.title) + + fill_in 'Description', with: MergeRequest.reference_prefix + wait_for_requests + expect(find_autocomplete_menu).to be_visible + expect_autocomplete_entry(merge_request.title) + end + end + + before_all do + group.add_maintainer(user) + end + + describe 'new milestone page' do + before do + sign_in(user) + visit new_group_milestone_path(group) + + wait_for_requests + end + + it_behaves_like 'displays autocomplete menu for all entities' + end + + describe 'update milestone page' do + before do + sign_in(user) + visit edit_group_milestone_path(group, milestone) + + wait_for_requests + end + + it_behaves_like 'displays autocomplete menu for all entities' + end + + private + + def find_autocomplete_menu + find('.atwho-view ul', visible: true) + end + + def expect_autocomplete_entry(entry) + page.within('.atwho-container') do + expect(page).to have_content(entry) + end + end +end diff --git a/spec/features/groups/navbar_spec.rb b/spec/features/groups/navbar_spec.rb index 021b1af54d4..b46d4dae87a 100644 --- a/spec/features/groups/navbar_spec.rb +++ b/spec/features/groups/navbar_spec.rb @@ -13,21 +13,10 @@ RSpec.describe 'Group navbar' do let(:structure) do [ - { - nav_item: _('Group overview'), - nav_sub_items: [ - _('Details'), - _('Activity') - ] - }, + group_information_nav_item, { nav_item: _('Issues'), - nav_sub_items: [ - _('List'), - _('Board'), - _('Labels'), - _('Milestones') - ] + nav_sub_items: issues_nav_items }, { nav_item: _('Merge requests'), @@ -40,11 +29,12 @@ RSpec.describe 'Group navbar' do nav_sub_items: [] }, (analytics_nav_item if Gitlab.ee?), - { - nav_item: _('Members'), - nav_sub_items: [] - } - ] + members_nav_item + ].compact + end + + let(:members_nav_item) do + nil end before do @@ -87,4 +77,40 @@ RSpec.describe 'Group navbar' do it_behaves_like 'verified navigation bar' end + + context 'when feature flag :sidebar_refactor is disabled' do + let(:group_information_nav_item) do + { + nav_item: _('Group overview'), + nav_sub_items: [ + _('Details'), + _('Activity') + ] + } + end + + let(:members_nav_item) do + { + nav_item: _('Members'), + nav_sub_items: [] + } + end + + let(:issues_nav_items) do + [ + _('List'), + _('Board'), + _('Labels'), + _('Milestones') + ] + end + + before do + stub_feature_flags(sidebar_refactor: false) + + visit group_path(group) + end + + it_behaves_like 'verified navigation bar' + end end diff --git a/spec/features/groups/settings/packages_and_registries_spec.rb b/spec/features/groups/settings/packages_and_registries_spec.rb index 45ea77e3868..551a0bc5375 100644 --- a/spec/features/groups/settings/packages_and_registries_spec.rb +++ b/spec/features/groups/settings/packages_and_registries_spec.rb @@ -66,28 +66,31 @@ RSpec.describe 'Group Packages & Registries settings' do it 'automatically saves changes to the server', :js do visit_settings_page - expect(page).to have_content('Allow duplicates') + within '[data-testid="maven-settings"]' do + expect(page).to have_content('Allow duplicates') - find('.gl-toggle').click + find('.gl-toggle').click - expect(page).to have_content('Do not allow duplicates') + expect(page).to have_content('Do not allow duplicates') - visit_settings_page + visit_settings_page - expect(page).to have_content('Do not allow duplicates') + expect(page).to have_content('Do not allow duplicates') + end end it 'shows an error on wrong regex', :js do visit_settings_page - expect(page).to have_content('Allow duplicates') - - find('.gl-toggle').click + within '[data-testid="maven-settings"]' do + expect(page).to have_content('Allow duplicates') - expect(page).to have_content('Do not allow duplicates') + find('.gl-toggle').click - fill_in 'Exceptions', with: ')' + expect(page).to have_content('Do not allow duplicates') + fill_in 'Exceptions', with: ')' + end # simulate blur event find('body').click @@ -98,11 +101,13 @@ RSpec.describe 'Group Packages & Registries settings' do it 'works correctly', :js do visit_sub_group_settings_page - expect(page).to have_content('Allow duplicates') + within '[data-testid="maven-settings"]' do + expect(page).to have_content('Allow duplicates') - find('.gl-toggle').click + find('.gl-toggle').click - expect(page).to have_content('Do not allow duplicates') + expect(page).to have_content('Do not allow duplicates') + end end end end diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 33d2ac50628..bcccadf7710 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Group' do - let_it_be(:user) { create(:user) } + let(:user) { create(:user) } before do sign_in(user) @@ -368,21 +368,21 @@ RSpec.describe 'Group' do expect(page).to have_content(nested_group.name) expect(page).to have_content(project.name) - expect(page).to have_link('Group overview') + expect(page).to have_link('Group information') end - it 'renders subgroup page with the text "Subgroup overview"' do + it 'renders subgroup page with the text "Subgroup information"' do visit group_path(nested_group) wait_for_requests - expect(page).to have_link('Subgroup overview') + expect(page).to have_link('Subgroup information') end - it 'renders project page with the text "Project overview"' do + it 'renders project page with the text "Project information"' do visit project_path(project) wait_for_requests - expect(page).to have_link('Project overview') + expect(page).to have_link('Project information') end end @@ -439,6 +439,35 @@ RSpec.describe 'Group' do end end + describe 'new_repo experiment' do + let_it_be(:group) { create_default(:group) } + + it 'when in candidate renders "project/repository"' do + stub_experiments(new_repo: :candidate) + + visit group_path(group) + + find('li.header-new.dropdown').click + + page.within('li.header-new.dropdown') do + expect(page).to have_selector('a', text: 'New project/repository') + end + end + + it 'when in control renders "project/repository"' do + stub_experiments(new_repo: :control) + + visit group_path(group) + + find('li.header-new.dropdown').click + + page.within('li.header-new.dropdown') do + expect(page).to have_selector('a', text: 'New project') + expect(page).to have_no_selector('a', text: 'New project/repository') + end + end + end + def remove_with_confirm(button_text, confirm_with) click_button button_text fill_in 'confirm_name_input', with: confirm_with diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb index e9960802378..a72cf033d61 100644 --- a/spec/features/invites_spec.rb +++ b/spec/features/invites_spec.rb @@ -3,18 +3,16 @@ require 'spec_helper' RSpec.describe 'Group or Project invitations', :aggregate_failures do - let(:user) { create(:user, email: 'user@example.com') } - let(:owner) { create(:user, name: 'John Doe') } - let(:group) { create(:group, name: 'Owned') } - let(:project) { create(:project, :repository, namespace: group) } + let_it_be(:owner) { create(:user, name: 'John Doe') } + let_it_be(:group) { create(:group, name: 'Owned') } + let_it_be(:project) { create(:project, :repository, namespace: group) } + 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) - group_invite.generate_invite_token! end def confirm_email(new_user) @@ -23,13 +21,13 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do visit user_confirmation_path(confirmation_token: new_user_token) end - def fill_in_sign_up_form(new_user) + def fill_in_sign_up_form(new_user, submit_button_text = 'Register') fill_in 'new_user_first_name', with: new_user.first_name fill_in 'new_user_last_name', with: new_user.last_name fill_in 'new_user_username', with: new_user.username fill_in 'new_user_email', with: new_user.email fill_in 'new_user_password', with: new_user.password - click_button 'Register' + click_button submit_button_text end def fill_in_sign_in_form(user) @@ -44,46 +42,128 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do click_button 'Get started!' end - context 'when signed out' do + context 'when inviting a registered user' do + let(:invite_email) { 'user@example.com' } + before do - visit invite_path(group_invite.raw_invite_token) + group.add_developer(invite_email, owner) + group_invite.generate_invite_token! end - it 'renders sign in page with sign in notice' do - expect(current_path).to eq(new_user_registration_path) - expect(page).to have_content('To accept this invitation, create an account or sign in') - end + context 'when signed out' do + context 'when analyzing the redirects and forms from invite link click' do + before do + visit invite_path(group_invite.raw_invite_token) + end - it 'pre-fills the "Username or email" field on the sign in box with the invite_email from the invite' do - click_link 'Sign in' + it 'renders sign up page with sign up notice' do + expect(current_path).to eq(new_user_registration_path) + expect(page).to have_content('To accept this invitation, create an account or sign in') + end - expect(find_field('Username or email').value).to eq(group_invite.invite_email) - end + it 'pre-fills the "Username or email" field on the sign in box with the invite_email from the invite' do + click_link 'Sign in' - it 'pre-fills the Email field on the sign up box with the invite_email from the invite' do - expect(find_field('Email').value).to eq(group_invite.invite_email) - end + expect(find_field('Username or email').value).to eq(group_invite.invite_email) + end - it 'sign in, grants access and redirects to group page' do - click_link 'Sign in' + it 'pre-fills the Email field on the sign up box with the invite_email from the invite' do + expect(find_field('Email').value).to eq(group_invite.invite_email) + end + end - fill_in_sign_in_form(user) + context 'when invite is sent before account is created - ldap or social sign in for manual acceptance edge case' do + let(:user) { create(:user, email: 'user@example.com') } - expect(current_path).to eq(group_path(group)) - expect(page).to have_content('You have been granted Developer access to group Owned.') - end - end + context 'when invite clicked and not signed in' do + before do + visit invite_path(group_invite.raw_invite_token) + end - context 'when signed in as an existing member' do - before do - sign_in(owner) - end + it 'sign in, grants access and redirects to group activity page' do + click_link 'Sign in' - it 'shows message user already a member' do - visit invite_path(group_invite.raw_invite_token) + fill_in_sign_in_form(user) + + expect(current_path).to eq(activity_group_path(group)) + end + end + + context 'when signed in and an invite link is clicked' do + context 'when an invite email is a secondary email for the user' do + let(:invite_email) { 'user_secondary@example.com' } + + before do + sign_in(user) + visit invite_path(group_invite.raw_invite_token) + end + + it 'sends user to the invite url and allows them to decline' do + expect(current_path).to eq(invite_path(group_invite.raw_invite_token)) + expect(page).to have_content("Note that this invitation was sent to #{invite_email}") + expect(page).to have_content("but you are signed in as #{user.to_reference} with email #{user.email}") + + click_link('Decline') + + expect(page).to have_content('You have declined the invitation') + expect(current_path).to eq(dashboard_projects_path) + expect { group_invite.reload }.to raise_error ActiveRecord::RecordNotFound + end + + it 'sends uer to the invite url and allows them to accept' do + expect(current_path).to eq(invite_path(group_invite.raw_invite_token)) + expect(page).to have_content("Note that this invitation was sent to #{invite_email}") + expect(page).to have_content("but you are signed in as #{user.to_reference} with email #{user.email}") + + click_link('Accept invitation') + + expect(page).to have_content('You have been granted') + expect(current_path).to eq(activity_group_path(group)) + end + end + + context 'when user is an existing member' do + before do + sign_in(owner) + visit invite_path(group_invite.raw_invite_token) + end + + it 'shows message user already a member' do + expect(current_path).to eq(invite_path(group_invite.raw_invite_token)) + expect(page).to have_link(owner.name, href: user_url(owner)) + expect(page).to have_content('However, you are already a member of this group.') + end + end + end + + context 'when declining the invitation from invitation reminder email' do + context 'when signed in' do + before do + sign_in(user) + visit decline_invite_path(group_invite.raw_invite_token) + end + + it 'declines application and redirects to dashboard' do + expect(current_path).to eq(dashboard_projects_path) + expect(page).to have_content('You have declined the invitation to join group Owned.') + expect { group_invite.reload }.to raise_error ActiveRecord::RecordNotFound + end + end + + context 'when signed out with signup onboarding' do + before do + visit decline_invite_path(group_invite.raw_invite_token) + end - expect(page).to have_link(owner.name, href: user_url(owner)) - expect(page).to have_content('However, you are already a member of this group.') + it 'declines application and redirects to sign in page' do + expect(current_path).to eq(decline_invite_path(group_invite.raw_invite_token)) + expect(page).not_to have_content('You have declined the invitation to join') + expect(page).to have_content('You successfully declined the invitation') + expect { group_invite.reload }.to raise_error ActiveRecord::RecordNotFound + end + end + end + end end end @@ -91,12 +171,15 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do let(:new_user) { build_stubbed(:user) } let(:invite_email) { new_user.email } let(:group_invite) { create(:group_member, :invited, group: group, invite_email: invite_email, created_by: owner) } - let!(:project_invite) { create(:project_member, :invited, project: project, invite_email: invite_email) } + let(:send_email_confirmation) { true } + + before do + stub_application_setting(send_user_confirmation_email: send_email_confirmation) + end context 'when registering using invitation email' do before do - stub_application_setting(send_user_confirmation_email: send_email_confirmation) - visit invite_path(group_invite.raw_invite_token) + visit invite_path(group_invite.raw_invite_token, invite_type: Members::InviteEmailExperiment::INVITE_TYPE) end context 'with admin approval required enabled' do @@ -104,8 +187,6 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures 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) @@ -117,79 +198,42 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do context 'email confirmation disabled' do let(:send_email_confirmation) { false } - it 'signs up and redirects to the dashboard page with all the projects/groups invitations automatically accepted' do + it 'signs up and redirects to the most recent membership activity page with all the projects/groups invitations automatically accepted' do fill_in_sign_up_form(new_user) fill_in_welcome_form - expect(current_path).to eq(dashboard_projects_path) - expect(page).to have_content(project.full_name) - - visit group_path(group) - - expect(page).to have_content(group.full_name) + expect(current_path).to eq(activity_group_path(group)) + expect(page).to have_content('You have been granted Owner access to group Owned.') end context 'the user sign-up using a different email address' do let(:invite_email) { build_stubbed(:user).email } - it 'signs up and redirects to the invitation page' do + it 'signs up and redirects to the activity page' do fill_in_sign_up_form(new_user) fill_in_welcome_form - expect(current_path).to eq(invite_path(group_invite.raw_invite_token)) + expect(current_path).to eq(activity_group_path(group)) end end end context 'email confirmation enabled' do - let(:send_email_confirmation) { true } - - context 'when soft email confirmation is not enabled' do - before do - allow(User).to receive(:allow_unconfirmed_access_for).and_return 0 - end + context 'with members/invite_email experiment', :experiment do + it 'tracks the accepted invite' do + expect(experiment('members/invite_email')).to track(:accepted) + .with_context(actor: group_invite) + .on_next_instance - it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do fill_in_sign_up_form(new_user) - confirm_email(new_user) - fill_in_sign_in_form(new_user) - fill_in_welcome_form - - expect(current_path).to eq(root_path) - expect(page).to have_content(project.full_name) - - visit group_path(group) - - expect(page).to have_content(group.full_name) end end - context 'when soft email confirmation is enabled' do - before do - allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days - end - - it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do - fill_in_sign_up_form(new_user) - fill_in_welcome_form - confirm_email(new_user) - - expect(current_path).to eq(root_path) - expect(page).to have_content(project.full_name) - - visit group_path(group) - - expect(page).to have_content(group.full_name) - end - end - - it "doesn't accept invitations until the user confirms their email" do + it 'signs up and redirects to the group activity page with all the project/groups invitation automatically accepted' do fill_in_sign_up_form(new_user) fill_in_welcome_form - sign_in(owner) - visit project_project_members_path(project) - expect(page).to have_content 'Invited' + expect(current_path).to eq(activity_group_path(group)) end context 'the user sign-up using a different email address' do @@ -201,13 +245,13 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do allow(User).to receive(:allow_unconfirmed_access_for).and_return 0 end - it 'signs up and redirects to the invitation page' do + it 'signs up and redirects to the group activity page' do fill_in_sign_up_form(new_user) confirm_email(new_user) fill_in_sign_in_form(new_user) fill_in_welcome_form - expect(current_path).to eq(invite_path(group_invite.raw_invite_token)) + expect(current_path).to eq(activity_group_path(group)) end end @@ -217,78 +261,75 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days end - it 'signs up and redirects to the invitation page' do + it 'signs up and redirects to the group activity page' do fill_in_sign_up_form(new_user) fill_in_welcome_form - expect(current_path).to eq(invite_path(group_invite.raw_invite_token)) + expect(current_path).to eq(activity_group_path(group)) end end end end end - context 'when declining the invitation' do - let(:send_email_confirmation) { true } + context 'with invite_signup_page_interaction experiment on', :experiment do + context 'with control experience' do + before do + stub_experiments(invite_signup_page_interaction: :control) + end - context 'as an existing user' do - let(:group_invite) { create(:group_member, user: user, group: group, created_by: owner) } + it 'lands on invite sign up page and tracks the accepted invite' do + expect(experiment(:invite_signup_page_interaction)).to track(:view) + .with_context(actor: group_invite) + .on_next_instance - context 'when signed in' do - before do - sign_in(user) - visit decline_invite_path(group_invite.raw_invite_token) - end + visit invite_path(group_invite.raw_invite_token) - it 'declines application and redirects to dashboard' do - expect(current_path).to eq(dashboard_projects_path) - expect(page).to have_content('You have declined the invitation to join group Owned.') - expect { group_invite.reload }.to raise_error ActiveRecord::RecordNotFound - end - end + expect(current_path).to eq(new_user_registration_path) - context 'when signed out' do - before do - visit decline_invite_path(group_invite.raw_invite_token) - end + expect(experiment(:invite_signup_page_interaction)).to track(:form_submission) + .with_context(actor: group_invite) + .on_next_instance - it 'declines application and redirects to sign in page' do - expect(current_path).to eq(new_user_session_path) - expect(page).to have_content('You have declined the invitation to join group Owned.') - expect { group_invite.reload }.to raise_error ActiveRecord::RecordNotFound - end + fill_in_sign_up_form(new_user, 'Register') + + expect(current_path).to eq(users_sign_up_welcome_path) end end - context 'as a non-existing user' do + context 'with candidate experience on .com' do before do - visit decline_invite_path(group_invite.raw_invite_token) + allow(Gitlab).to receive(:dev_env_or_com?).and_return(true) + stub_experiments(invite_signup_page_interaction: :candidate) end - it 'declines application and shows a decline page' do - expect(current_path).to eq(decline_invite_path(group_invite.raw_invite_token)) - expect(page).to have_content('You successfully declined the invitation') - expect { group_invite.reload }.to raise_error ActiveRecord::RecordNotFound - end - end - end + it 'lands on invite sign up page and tracks the accepted invite' do + expect(experiment(:invite_signup_page_interaction)).to track(:view) + .with_context(actor: group_invite) + .on_next_instance - context 'when accepting the invitation' do - let(:send_email_confirmation) { true } + visit invite_path(group_invite.raw_invite_token) - before do - sign_in(user) - visit invite_path(group_invite.raw_invite_token) - end + expect(current_path).to eq(new_users_sign_up_invite_path) + + expect(experiment(:invite_signup_page_interaction)).to track(:form_submission) + .with_context(actor: group_invite) + .on_next_instance + + fill_in_sign_up_form(new_user, 'Continue') - it 'grants access and redirects to group page' do - expect(group.users.include?(user)).to be false + expect(current_path).to eq(users_sign_up_welcome_path) + end + end + end - page.click_link 'Accept invitation' + context 'when declining the invitation from invitation reminder email' do + it 'declines application and shows a decline page' do + visit decline_invite_path(group_invite.raw_invite_token) - expect(current_path).to eq(group_path(group)) - expect(page).to have_content('You have been granted Owner access to group Owned.') - expect(group.users.include?(user)).to be true + expect(current_path).to eq(decline_invite_path(group_invite.raw_invite_token)) + expect(page).to have_content('You successfully declined the invitation') + expect { group_invite.reload }.to raise_error ActiveRecord::RecordNotFound end end end diff --git a/spec/features/issuables/sorting_list_spec.rb b/spec/features/issuables/sorting_list_spec.rb index d065e96885c..6e07c6ffed2 100644 --- a/spec/features/issuables/sorting_list_spec.rb +++ b/spec/features/issuables/sorting_list_spec.rb @@ -57,7 +57,7 @@ RSpec.describe 'Sort Issuable List' do it 'is "last updated"' do visit_merge_requests_with_state(project, 'merged') - expect(find('.filter-dropdown-container')).to have_content('Last updated') + expect(page).to have_button 'Last updated' expect(first_merge_request).to include(last_updated_issuable.title) expect(last_merge_request).to include(first_updated_issuable.title) end @@ -69,7 +69,7 @@ RSpec.describe 'Sort Issuable List' do it 'is "last updated"' do visit_merge_requests_with_state(project, 'closed') - expect(find('.filter-dropdown-container')).to have_content('Last updated') + expect(page).to have_button 'Last updated' expect(first_merge_request).to include(last_updated_issuable.title) expect(last_merge_request).to include(first_updated_issuable.title) end @@ -81,7 +81,7 @@ RSpec.describe 'Sort Issuable List' do it 'is "created date"' do visit_merge_requests_with_state(project, 'all') - expect(find('.filter-dropdown-container')).to have_content('Created date') + expect(page).to have_button 'Created date' expect(first_merge_request).to include(last_created_issuable.title) expect(last_merge_request).to include(first_created_issuable.title) end @@ -94,15 +94,13 @@ RSpec.describe 'Sort Issuable List' do it 'supports sorting in asc and desc order' do visit_merge_requests_with_state(project, 'open') - page.within('.filter-dropdown-container') do - click_button('Created date') - click_link('Last updated') - end + click_button('Created date') + click_link('Last updated') expect(first_merge_request).to include(last_updated_issuable.title) expect(last_merge_request).to include(first_updated_issuable.title) - find('.filter-dropdown-container .rspec-reverse-sort').click + click_on 'Sort direction' expect(first_merge_request).to include(first_updated_issuable.title) expect(last_merge_request).to include(last_updated_issuable.title) @@ -133,7 +131,7 @@ RSpec.describe 'Sort Issuable List' do it 'is "created date"' do visit_issues project - expect(find('.filter-dropdown-container')).to have_content('Created date') + expect(page).to have_button 'Created date' expect(first_issue).to include(last_created_issuable.title) expect(last_issue).to include(first_created_issuable.title) end @@ -145,7 +143,7 @@ RSpec.describe 'Sort Issuable List' do it 'is "created date"' do visit_issues_with_state(project, 'opened') - expect(find('.filter-dropdown-container')).to have_content('Created date') + expect(page).to have_button 'Created date' expect(first_issue).to include(last_created_issuable.title) expect(last_issue).to include(first_created_issuable.title) end @@ -157,7 +155,7 @@ RSpec.describe 'Sort Issuable List' do it 'is "last updated"' do visit_issues_with_state(project, 'closed') - expect(find('.filter-dropdown-container')).to have_content('Last updated') + expect(page).to have_button 'Last updated' expect(first_issue).to include(last_updated_issuable.title) expect(last_issue).to include(first_updated_issuable.title) end @@ -169,7 +167,7 @@ RSpec.describe 'Sort Issuable List' do it 'is "created date"' do visit_issues_with_state(project, 'all') - expect(find('.filter-dropdown-container')).to have_content('Created date') + expect(page).to have_button 'Created date' expect(first_issue).to include(last_created_issuable.title) expect(last_issue).to include(first_created_issuable.title) end @@ -183,7 +181,7 @@ RSpec.describe 'Sort Issuable List' do end it 'shows the sort order as created date' do - expect(find('.filter-dropdown-container')).to have_content('Created date') + expect(page).to have_button 'Created date' expect(first_issue).to include(last_created_issuable.title) expect(last_issue).to include(first_created_issuable.title) end @@ -196,15 +194,17 @@ RSpec.describe 'Sort Issuable List' do it 'supports sorting in asc and desc order' do visit_issues_with_state(project, 'opened') - page.within('.filter-dropdown-container') do - click_button('Created date') - click_link('Last updated') - end + click_button('Created date') + click_on('Last updated') + + wait_for_requests expect(first_issue).to include(last_updated_issuable.title) expect(last_issue).to include(first_updated_issuable.title) - find('.filter-dropdown-container .rspec-reverse-sort').click + click_on 'Sort direction' + + wait_for_requests expect(first_issue).to include(first_updated_issuable.title) expect(last_issue).to include(last_updated_issuable.title) diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb deleted file mode 100644 index 80bf964e2ee..00000000000 --- a/spec/features/issues/bulk_assignment_labels_spec.rb +++ /dev/null @@ -1,475 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Issues > Labels bulk assignment' do - let(:user) { create(:user) } - let!(:project) { create(:project) } - let!(:bug) { create(:label, project: project, title: 'bug') } - let!(:feature) { create(:label, project: project, title: 'feature') } - let!(:frontend) { create(:label, project: project, title: 'frontend') } - let!(:wontfix) { create(:label, project: project, title: 'wontfix') } - let!(:issue1) { create(:issue, project: project, title: "Issue 1", labels: [frontend]) } - let!(:issue2) { create(:issue, project: project, title: "Issue 2") } - - context 'as an allowed user', :js do - before do - project.add_maintainer(user) - - sign_in user - end - - context 'sidebar' do - before do - enable_bulk_update - end - - it 'is present when bulk edit is enabled' do - expect(page).to have_css('.issuable-sidebar') - end - - it 'is not present when bulk edit is disabled' do - disable_bulk_update - expect(page).not_to have_css('.issuable-sidebar') - end - end - - context 'can bulk assign' do - before do - enable_bulk_update - end - - context 'a label' do - context 'to all issues' do - before do - check 'check-all-issues' - open_labels_dropdown ['bug'] - update_issues - end - - it do - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - expect(find("#issue_#{issue1.id}")).to have_content 'frontend' - expect(find("#issue_#{issue2.id}")).to have_content 'bug' - expect(find("#issue_#{issue2.id}")).not_to have_content 'frontend' - end - end - - context 'to some issues' do - before do - check "selected_issue_#{issue1.id}" - check "selected_issue_#{issue2.id}" - open_labels_dropdown ['bug'] - update_issues - end - - it do - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - expect(find("#issue_#{issue1.id}")).to have_content 'frontend' - expect(find("#issue_#{issue2.id}")).to have_content 'bug' - expect(find("#issue_#{issue2.id}")).not_to have_content 'frontend' - end - end - - context 'to an issue' do - before do - check "selected_issue_#{issue1.id}" - open_labels_dropdown ['bug'] - update_issues - end - - it do - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - expect(find("#issue_#{issue1.id}")).to have_content 'frontend' - expect(find("#issue_#{issue2.id}")).not_to have_content 'bug' - expect(find("#issue_#{issue2.id}")).not_to have_content 'frontend' - end - end - - context 'to an issue by selecting the label first' do - before do - open_labels_dropdown ['bug'] - check "selected_issue_#{issue1.id}" - update_issues - end - - it do - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - expect(find("#issue_#{issue1.id}")).to have_content 'frontend' - expect(find("#issue_#{issue2.id}")).not_to have_content 'bug' - expect(find("#issue_#{issue2.id}")).not_to have_content 'frontend' - end - end - end - - context 'multiple labels' do - context 'to all issues' do - before do - check 'check-all-issues' - open_labels_dropdown %w(bug feature) - update_issues - end - - it do - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - expect(find("#issue_#{issue1.id}")).to have_content 'feature' - expect(find("#issue_#{issue2.id}")).to have_content 'bug' - expect(find("#issue_#{issue2.id}")).to have_content 'feature' - end - end - - context 'to a issue' do - before do - check "selected_issue_#{issue1.id}" - open_labels_dropdown %w(bug feature) - update_issues - end - - it do - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - expect(find("#issue_#{issue1.id}")).to have_content 'feature' - expect(find("#issue_#{issue2.id}")).not_to have_content 'bug' - expect(find("#issue_#{issue2.id}")).not_to have_content 'feature' - end - end - end - end - - context 'can assign a label to all issues when label is present' do - before do - issue2.labels << bug - issue2.labels << feature - - enable_bulk_update - check 'check-all-issues' - - open_labels_dropdown ['bug'] - update_issues - end - - it do - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - expect(find("#issue_#{issue2.id}")).to have_content 'bug' - end - end - - context 'can bulk un-assign' do - context 'all labels to all issues' do - before do - issue1.labels << bug - issue1.labels << feature - issue2.labels << bug - issue2.labels << feature - - enable_bulk_update - check 'check-all-issues' - unmark_labels_in_dropdown %w(bug feature) - update_issues - end - - it do - expect(find("#issue_#{issue1.id}")).not_to have_content 'bug' - expect(find("#issue_#{issue1.id}")).not_to have_content 'feature' - expect(find("#issue_#{issue2.id}")).not_to have_content 'bug' - expect(find("#issue_#{issue2.id}")).not_to have_content 'feature' - end - end - - context 'a label to a issue' do - before do - issue1.labels << bug - issue2.labels << feature - - enable_bulk_update - check_issue issue1 - unmark_labels_in_dropdown ['bug'] - update_issues - end - - it do - expect(find("#issue_#{issue1.id}")).not_to have_content 'bug' - expect(find("#issue_#{issue2.id}")).to have_content 'feature' - end - end - - context 'a label and keep the others label' do - before do - issue1.labels << bug - issue1.labels << feature - issue2.labels << bug - issue2.labels << feature - - enable_bulk_update - check_issue issue1 - check_issue issue2 - unmark_labels_in_dropdown ['bug'] - update_issues - end - - it do - expect(find("#issue_#{issue1.id}")).not_to have_content 'bug' - expect(find("#issue_#{issue1.id}")).to have_content 'feature' - expect(find("#issue_#{issue2.id}")).not_to have_content 'bug' - expect(find("#issue_#{issue2.id}")).to have_content 'feature' - end - end - end - - context 'toggling a milestone' do - let!(:milestone) { create(:milestone, project: project, title: 'First Release') } - - context 'setting a milestone' do - before do - issue1.labels << bug - issue2.labels << feature - enable_bulk_update - end - - it 'keeps labels' do - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - expect(find("#issue_#{issue2.id}")).to have_content 'feature' - - check 'check-all-issues' - - open_milestone_dropdown(['First Release']) - update_issues - - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - expect(find("#issue_#{issue1.id}")).to have_content 'First Release' - expect(find("#issue_#{issue2.id}")).to have_content 'feature' - expect(find("#issue_#{issue2.id}")).to have_content 'First Release' - end - end - - context 'setting a milestone and adding another label' do - before do - issue1.labels << bug - enable_bulk_update - end - - it 'keeps existing label and new label is present' do - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - - check 'check-all-issues' - open_milestone_dropdown ['First Release'] - open_labels_dropdown ['feature'] - update_issues - - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - expect(find("#issue_#{issue1.id}")).to have_content 'feature' - expect(find("#issue_#{issue1.id}")).to have_content 'First Release' - expect(find("#issue_#{issue2.id}")).to have_content 'feature' - expect(find("#issue_#{issue2.id}")).to have_content 'First Release' - end - end - - context 'setting a milestone and removing existing label' do - before do - issue1.labels << bug - issue1.labels << feature - issue2.labels << feature - - enable_bulk_update - end - - it 'keeps existing label and new label is present' do - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - expect(find("#issue_#{issue2.id}")).to have_content 'feature' - - check 'check-all-issues' - - open_milestone_dropdown ['First Release'] - unmark_labels_in_dropdown ['feature'] - update_issues - - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - expect(find("#issue_#{issue1.id}")).not_to have_content 'feature' - expect(find("#issue_#{issue1.id}")).to have_content 'First Release' - expect(find("#issue_#{issue2.id}")).not_to have_content 'feature' - expect(find("#issue_#{issue2.id}")).to have_content 'First Release' - end - end - - context 'unsetting a milestone' do - before do - issue1.milestone = milestone - issue2.milestone = milestone - issue1.save! - issue2.save! - issue1.labels << bug - issue2.labels << feature - - enable_bulk_update - end - - it 'keeps labels' do - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - expect(find("#issue_#{issue1.id}")).to have_content 'First Release' - expect(find("#issue_#{issue2.id}")).to have_content 'feature' - expect(find("#issue_#{issue2.id}")).to have_content 'First Release' - - check 'check-all-issues' - open_milestone_dropdown(['No milestone']) - update_issues - - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - expect(find("#issue_#{issue1.id}")).not_to have_content 'First Release' - expect(find("#issue_#{issue2.id}")).to have_content 'feature' - expect(find("#issue_#{issue2.id}")).not_to have_content 'First Release' - end - end - end - - context 'toggling checked issues' do - before do - issue1.labels << bug - enable_bulk_update - end - - it do - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - - check_issue issue1 - open_labels_dropdown ['feature'] - uncheck_issue issue1 - check_issue issue1 - update_issues - sleep 1 # needed - - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - expect(find("#issue_#{issue1.id}")).to have_content 'feature' - end - end - - context 'mark previously toggled label' do - before do - enable_bulk_update - end - - it do - open_labels_dropdown ['feature'] - - check_issue issue1 - - update_issues - - expect(find("#issue_#{issue1.id}")).to have_content 'feature' - end - end - - # Special case https://gitlab.com/gitlab-org/gitlab-foss/issues/24877 - context 'unmarking common label' do - before do - issue1.labels << bug - issue1.labels << feature - issue2.labels << bug - - enable_bulk_update - end - - it 'applies label from filtered results' do - check 'check-all-issues' - - page.within('.issues-bulk-update') do - click_button 'Select labels' - wait_for_requests - - expect(find('.dropdown-menu-labels li', text: 'bug')).to have_css('.is-active') - expect(find('.dropdown-menu-labels li', text: 'feature')).to have_css('.is-indeterminate') - - click_link 'bug' - find('.dropdown-input-field', visible: true).set('wontfix') - click_link 'wontfix' - end - - update_issues - - page.within '.issues-holder' do - expect(find("#issue_#{issue1.id}")).not_to have_content 'bug' - expect(find("#issue_#{issue1.id}")).to have_content 'feature' - expect(find("#issue_#{issue1.id}")).to have_content 'wontfix' - - expect(find("#issue_#{issue2.id}")).not_to have_content 'bug' - expect(find("#issue_#{issue2.id}")).not_to have_content 'feature' - expect(find("#issue_#{issue2.id}")).to have_content 'wontfix' - end - end - end - end - - context 'as a guest' do - before do - sign_in user - - visit project_issues_path(project) - end - - context 'cannot bulk assign labels' do - it do - expect(page).not_to have_button 'Edit issues' - expect(page).not_to have_css '.check-all-issues' - expect(page).not_to have_css '.issue-check' - end - end - end - - def open_milestone_dropdown(items = []) - page.within('.issues-bulk-update') do - click_button 'Select milestone' - wait_for_requests - items.map do |item| - click_link item - end - end - end - - def open_labels_dropdown(items = [], unmark = false) - page.within('.issues-bulk-update') do - click_button 'Select labels' - wait_for_requests - items.map do |item| - click_link item - end - - if unmark - items.map do |item| - # Make sure we are unmarking the item no matter the state it has currently - click_link item until find('a', text: item)[:class].include? 'label-item' - end - end - end - end - - def unmark_labels_in_dropdown(items = []) - open_labels_dropdown(items, true) - end - - def check_issue(issue, uncheck = false) - page.within('.issues-list') do - if uncheck - uncheck "selected_issue_#{issue.id}" - else - check "selected_issue_#{issue.id}" - end - end - end - - def uncheck_issue(issue) - check_issue(issue, true) - end - - def update_issues - find('.update-selected-issues').click - wait_for_requests - end - - def enable_bulk_update - visit project_issues_path(project) - click_button 'Edit issues' - end - - def disable_bulk_update - click_button 'Cancel' - 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 34d78880991..a4c0a84af7d 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 @@ -72,7 +72,7 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j end it 'shows a warning that the merge request contains unresolved threads' do - expect(page).to have_content 'Before this can be merged,' + expect(page).to have_content 'all threads must be resolved' end it 'has a link to resolve all threads by creating an issue' do diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index 4f4584e7dce..88a7b890daa 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -79,26 +79,6 @@ RSpec.describe 'Filter issues', :js do expect_filtered_search_input(search_term) end - context 'with the NOT queries feature flag disabled' do - before do - stub_feature_flags(not_issuable_queries: false) - visit project_issues_path(project) - end - - it 'does not have the != option' do - input_filtered_search("label:", submit: false, extra_space: false) - - wait_for_requests - within('#js-dropdown-operator') do - tokens = all(:css, 'li.filter-dropdown-item') - expect(tokens.count).to eq(1) - button = tokens[0].find('button') - expect(button).to have_content('=') - expect(button).not_to have_content('!=') - end - end - end - describe 'filter issues by author' do context 'only author' do it 'filters issues by searched author' do @@ -350,7 +330,7 @@ RSpec.describe 'Filter issues', :js do context 'issue label clicked' do it 'filters and displays in search bar' do - find('[data-qa-selector="issuable-label"]', text: multiple_words_label.title).click + click_link multiple_words_label.title expect_issues_list_count(1) expect_tokens([label_token("\"#{multiple_words_label.title}\"")]) diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb index 04b4caa52fe..d147476f1ab 100644 --- a/spec/features/issues/issue_sidebar_spec.rb +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -5,17 +5,14 @@ require 'spec_helper' RSpec.describe 'Issue Sidebar' do include MobileHelpers - let(:group) { create(:group, :nested) } - let(:project) { create(:project, :public, namespace: group) } - let!(:user) { create(:user) } - let!(:label) { create(:label, project: project, title: 'bug') } - let(:issue) { create(:labeled_issue, project: project, labels: [label]) } - let!(:xss_label) { create(:label, project: project, title: '<script>alert("xss");</script>') } - let!(:milestone_expired) { create(:milestone, project: project, due_date: 5.days.ago) } - let!(:milestone_no_duedate) { create(:milestone, project: project, title: 'Foo - No due date') } - let!(:milestone1) { create(:milestone, project: project, title: 'Milestone-1', due_date: 20.days.from_now) } - let!(:milestone2) { create(:milestone, project: project, title: 'Milestone-2', due_date: 15.days.from_now) } - let!(:milestone3) { create(:milestone, project: project, title: 'Milestone-3', due_date: 10.days.from_now) } + let_it_be(:group) { create(:group, :nested) } + let_it_be(:project) { create(:project, :public, namespace: group) } + let_it_be(:user) { create(:user) } + let_it_be(:label) { create(:label, project: project, title: 'bug') } + let_it_be(:issue) { create(:labeled_issue, project: project, labels: [label]) } + let_it_be(:mock_date) { Date.today.at_beginning_of_month + 2.days } + let_it_be(:issue_with_due_date) { create(:issue, project: project, due_date: mock_date) } + let_it_be(:xss_label) { create(:label, project: project, title: '<script>alert("xss");</script>') } before do stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab") @@ -130,30 +127,7 @@ RSpec.describe 'Issue Sidebar' do end end - context 'when invite_members_version_b experiment is enabled' do - before do - stub_experiment_for_subject(invite_members_version_b: true) - end - - it 'shows a link for inviting members and follows through to modal' do - project.add_developer(user) - visit_issue(project, issue2) - - open_assignees_dropdown - - page.within '.dropdown-menu-user' do - expect(page).to have_link('Invite members', href: '#') - expect(page).to have_selector('[data-track-event="click_invite_members_version_b"]') - expect(page).to have_selector('[data-track-label="edit_assignee"]') - end - - click_link 'Invite members' - - expect(page).to have_content("Oops, this feature isn't ready yet") - end - end - - context 'when invite_members_version_b experiment is disabled' do + context 'when user cannot invite members in assignee dropdown' do it 'shows author in assignee dropdown and no invite link' do project.add_developer(user) visit_issue(project, issue2) @@ -212,7 +186,8 @@ RSpec.describe 'Issue Sidebar' do click_link user2.name end - find('.js-right-sidebar').click + find('.participants').click + wait_for_requests open_assignees_dropdown @@ -226,7 +201,31 @@ RSpec.describe 'Issue Sidebar' do end end - context 'as a allowed user' do + context 'due date widget', :js do + let(:due_date_value) { find('[data-testid="due-date"] [data-testid="sidebar-date-value"]') } + + context 'when no due date exists' do + before do + visit_issue(project, issue) + end + + it "displays 'None'" do + expect(due_date_value.text).to have_content 'None' + end + end + + context 'when due date exists' do + before do + visit_issue(project, issue_with_due_date) + end + + it "displays the due date" do + expect(due_date_value.text).to have_content mock_date.strftime('%b %-d, %Y') + end + end + end + + context 'as an allowed user' do before do project.add_developer(user) visit_issue(project, issue) @@ -260,6 +259,12 @@ RSpec.describe 'Issue Sidebar' do end context 'editing issue milestone', :js do + let_it_be(:milestone_expired) { create(:milestone, project: project, due_date: 5.days.ago) } + let_it_be(:milestone_no_duedate) { create(:milestone, project: project, title: 'Foo - No due date') } + let_it_be(:milestone1) { create(:milestone, project: project, title: 'Milestone-1', due_date: 20.days.from_now) } + let_it_be(:milestone2) { create(:milestone, project: project, title: 'Milestone-2', due_date: 15.days.from_now) } + let_it_be(:milestone3) { create(:milestone, project: project, title: 'Milestone-3', due_date: 10.days.from_now) } + before do page.within('.block.milestone > .title') do click_on 'Edit' @@ -448,6 +453,8 @@ RSpec.describe 'Issue Sidebar' do def visit_issue(project, issue) visit project_issue_path(project, issue) + + wait_for_requests end def open_issue_sidebar diff --git a/spec/features/issues/resource_label_events_spec.rb b/spec/features/issues/resource_label_events_spec.rb index 8faec85f3df..33edf2f0b63 100644 --- a/spec/features/issues/resource_label_events_spec.rb +++ b/spec/features/issues/resource_label_events_spec.rb @@ -40,7 +40,7 @@ RSpec.describe 'List issue resource label events', :js do labels.each { |label| click_link label } - click_on 'Edit' + send_keys(:escape) wait_for_requests end end diff --git a/spec/features/issues/service_desk_spec.rb b/spec/features/issues/service_desk_spec.rb index 75ea8c14f7f..0a879fdd4d4 100644 --- a/spec/features/issues/service_desk_spec.rb +++ b/spec/features/issues/service_desk_spec.rb @@ -9,6 +9,8 @@ RSpec.describe 'Service Desk Issue Tracker', :js do let_it_be(:support_bot) { User.support_bot } before do + stub_feature_flags(vue_issuables_list: true) + # The following two conditions equate to Gitlab::ServiceDesk.supported == true allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true) allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true) @@ -21,7 +23,7 @@ RSpec.describe 'Service Desk Issue Tracker', :js do before do visit project_path(project) find('.sidebar-top-level-items .shortcuts-issues').click - find('.sidebar-sub-level-items a[title="Service Desk"]').click + find('.sidebar-sub-level-items a', text: 'Service Desk').click end it 'can navigate to the service desk from link in the sidebar' do diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb index 461030d3176..70d7deadec3 100644 --- a/spec/features/issues/spam_issues_spec.rb +++ b/spec/features/issues/spam_issues_spec.rb @@ -14,6 +14,7 @@ RSpec.describe 'New issue', :js do Gitlab::CurrentSettings.update!( akismet_enabled: true, akismet_api_key: 'testkey', + spam_check_api_key: 'testkey', recaptcha_enabled: true, recaptcha_site_key: 'test site key', recaptcha_private_key: 'test private key' diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb deleted file mode 100644 index eb78e4e2456..00000000000 --- a/spec/features/issues/update_issues_spec.rb +++ /dev/null @@ -1,128 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Multiple issue updating from issues#index', :js do - let!(:project) { create(:project) } - let!(:issue) { create(:issue, project: project) } - let!(:user) { create(:user)} - - before do - project.add_maintainer(user) - sign_in(user) - end - - context 'status' do - it 'sets to closed' do - visit project_issues_path(project) - - click_button 'Edit issues' - find('#check-all-issues').click - find('.js-issue-status').click - - find('.dropdown-menu-status a', text: 'Closed').click - click_update_issues_button - expect(page).to have_selector('.issue', count: 0) - end - - it 'sets to open' do - create_closed - visit project_issues_path(project, state: 'closed') - - click_button 'Edit issues' - find('#check-all-issues').click - find('.js-issue-status').click - - find('.dropdown-menu-status a', text: 'Open').click - click_update_issues_button - expect(page).to have_selector('.issue', count: 0) - end - end - - context 'assignee' do - it 'updates to current user' do - visit project_issues_path(project) - - click_button 'Edit issues' - find('#check-all-issues').click - click_update_assignee_button - - find('.dropdown-menu-user-link', text: user.username).click - click_update_issues_button - - page.within('.issue .controls') do - expect(find('.author-link')['href']).to have_content(user.website_url) - end - end - - it 'updates to unassigned' do - create_assigned - visit project_issues_path(project) - - click_button 'Edit issues' - find('#check-all-issues').click - click_update_assignee_button - - click_link 'Unassigned' - click_update_issues_button - expect(find('.issue:first-child .controls')).not_to have_css('.author-link') - end - end - - context 'milestone' do - let!(:milestone) { create(:milestone, project: project) } - - it 'updates milestone' do - visit project_issues_path(project) - - click_button 'Edit issues' - find('#check-all-issues').click - find('.issues-bulk-update .js-milestone-select').click - - find('.dropdown-menu-milestone a', text: milestone.title).click - click_update_issues_button - - expect(page.find('.issue')).to have_content milestone.title - end - - it 'sets to no milestone' do - create_with_milestone - visit project_issues_path(project) - - wait_for_requests - - expect(first('.issue')).to have_content milestone.title - - click_button 'Edit issues' - find('#check-all-issues').click - find('.issues-bulk-update .js-milestone-select').click - - find('.dropdown-menu-milestone a', text: "No milestone").click - click_update_issues_button - - expect(find('.issue:first-child')).not_to have_content milestone.title - end - end - - def create_closed - create(:issue, project: project, state: :closed) - end - - def create_assigned - create(:issue, project: project, assignees: [user]) - end - - def create_with_milestone - create(:issue, project: project, milestone: milestone) - end - - def click_update_assignee_button - find('.js-update-assignee').click - wait_for_requests - end - - def click_update_issues_button - find('.update-selected-issues').click - wait_for_requests - end -end diff --git a/spec/features/issues/user_bulk_edits_issues_labels_spec.rb b/spec/features/issues/user_bulk_edits_issues_labels_spec.rb new file mode 100644 index 00000000000..97df2d0208b --- /dev/null +++ b/spec/features/issues/user_bulk_edits_issues_labels_spec.rb @@ -0,0 +1,468 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Issues > Labels bulk assignment' do + let(:user) { create(:user) } + let!(:project) { create(:project) } + let!(:bug) { create(:label, project: project, title: 'bug') } + let!(:feature) { create(:label, project: project, title: 'feature') } + let!(:frontend) { create(:label, project: project, title: 'frontend') } + let!(:wontfix) { create(:label, project: project, title: 'wontfix') } + let!(:issue1) { create(:issue, project: project, title: "Issue 1", labels: [frontend]) } + let!(:issue2) { create(:issue, project: project, title: "Issue 2") } + + context 'as an allowed user', :js do + before do + project.add_maintainer(user) + + sign_in user + end + + context 'sidebar' do + it 'is present when bulk edit is enabled' do + enable_bulk_update + expect(page).to have_css 'aside[aria-label="Bulk update"]' + end + + it 'is not present when bulk edit is disabled' do + expect(page).not_to have_css 'aside[aria-label="Bulk update"]' + end + end + + context 'can bulk assign' do + before do + enable_bulk_update + end + + context 'a label' do + context 'to all issues' do + before do + check 'Select all' + open_labels_dropdown ['bug'] + update_issues + end + + it do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'frontend' + expect(find("#issue_#{issue2.id}")).to have_content 'bug' + expect(find("#issue_#{issue2.id}")).not_to have_content 'frontend' + end + end + + context 'to some issues' do + before do + check issue1.title + check issue2.title + open_labels_dropdown ['bug'] + update_issues + end + + it do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'frontend' + expect(find("#issue_#{issue2.id}")).to have_content 'bug' + expect(find("#issue_#{issue2.id}")).not_to have_content 'frontend' + end + end + + context 'to an issue' do + before do + check issue1.title + open_labels_dropdown ['bug'] + update_issues + end + + it do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'frontend' + expect(find("#issue_#{issue2.id}")).not_to have_content 'bug' + expect(find("#issue_#{issue2.id}")).not_to have_content 'frontend' + end + end + + context 'to an issue by selecting the label first' do + before do + open_labels_dropdown ['bug'] + check issue1.title + update_issues + end + + it do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'frontend' + expect(find("#issue_#{issue2.id}")).not_to have_content 'bug' + expect(find("#issue_#{issue2.id}")).not_to have_content 'frontend' + end + end + end + + context 'multiple labels' do + context 'to all issues' do + before do + check 'Select all' + open_labels_dropdown %w(bug feature) + update_issues + end + + it do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'feature' + expect(find("#issue_#{issue2.id}")).to have_content 'bug' + expect(find("#issue_#{issue2.id}")).to have_content 'feature' + end + end + + context 'to a issue' do + before do + check issue1.title + open_labels_dropdown %w(bug feature) + update_issues + end + + it do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'feature' + expect(find("#issue_#{issue2.id}")).not_to have_content 'bug' + expect(find("#issue_#{issue2.id}")).not_to have_content 'feature' + end + end + end + end + + context 'can assign a label to all issues when label is present' do + before do + issue2.labels << bug + issue2.labels << feature + + enable_bulk_update + check 'Select all' + + open_labels_dropdown ['bug'] + update_issues + end + + it do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue2.id}")).to have_content 'bug' + end + end + + context 'can bulk un-assign' do + context 'all labels to all issues' do + before do + issue1.labels << bug + issue1.labels << feature + issue2.labels << bug + issue2.labels << feature + + enable_bulk_update + check 'Select all' + unmark_labels_in_dropdown %w(bug feature) + update_issues + end + + it do + expect(find("#issue_#{issue1.id}")).not_to have_content 'bug' + expect(find("#issue_#{issue1.id}")).not_to have_content 'feature' + expect(find("#issue_#{issue2.id}")).not_to have_content 'bug' + expect(find("#issue_#{issue2.id}")).not_to have_content 'feature' + end + end + + context 'a label to a issue' do + before do + issue1.labels << bug + issue2.labels << feature + + enable_bulk_update + check_issue issue1 + unmark_labels_in_dropdown ['bug'] + update_issues + end + + it do + expect(find("#issue_#{issue1.id}")).not_to have_content 'bug' + expect(find("#issue_#{issue2.id}")).to have_content 'feature' + end + end + + context 'a label and keep the others label' do + before do + issue1.labels << bug + issue1.labels << feature + issue2.labels << bug + issue2.labels << feature + + enable_bulk_update + check_issue issue1 + check_issue issue2 + unmark_labels_in_dropdown ['bug'] + update_issues + end + + it do + expect(find("#issue_#{issue1.id}")).not_to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'feature' + expect(find("#issue_#{issue2.id}")).not_to have_content 'bug' + expect(find("#issue_#{issue2.id}")).to have_content 'feature' + end + end + end + + context 'toggling a milestone' do + let!(:milestone) { create(:milestone, project: project, title: 'First Release') } + + context 'setting a milestone' do + before do + issue1.labels << bug + issue2.labels << feature + enable_bulk_update + end + + it 'keeps labels' do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue2.id}")).to have_content 'feature' + + check 'Select all' + + open_milestone_dropdown(['First Release']) + update_issues + + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'First Release' + expect(find("#issue_#{issue2.id}")).to have_content 'feature' + expect(find("#issue_#{issue2.id}")).to have_content 'First Release' + end + end + + context 'setting a milestone and adding another label' do + before do + issue1.labels << bug + enable_bulk_update + end + + it 'keeps existing label and new label is present' do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + + check 'Select all' + open_milestone_dropdown ['First Release'] + open_labels_dropdown ['feature'] + update_issues + + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'feature' + expect(find("#issue_#{issue1.id}")).to have_content 'First Release' + expect(find("#issue_#{issue2.id}")).to have_content 'feature' + expect(find("#issue_#{issue2.id}")).to have_content 'First Release' + end + end + + context 'setting a milestone and removing existing label' do + before do + issue1.labels << bug + issue1.labels << feature + issue2.labels << feature + + enable_bulk_update + end + + it 'keeps existing label and new label is present' do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue2.id}")).to have_content 'feature' + + check 'Select all' + + open_milestone_dropdown ['First Release'] + unmark_labels_in_dropdown ['feature'] + update_issues + + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).not_to have_content 'feature' + expect(find("#issue_#{issue1.id}")).to have_content 'First Release' + expect(find("#issue_#{issue2.id}")).not_to have_content 'feature' + expect(find("#issue_#{issue2.id}")).to have_content 'First Release' + end + end + + context 'unsetting a milestone' do + before do + issue1.milestone = milestone + issue2.milestone = milestone + issue1.save! + issue2.save! + issue1.labels << bug + issue2.labels << feature + + enable_bulk_update + end + + it 'keeps labels' do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'First Release' + expect(find("#issue_#{issue2.id}")).to have_content 'feature' + expect(find("#issue_#{issue2.id}")).to have_content 'First Release' + + check 'Select all' + open_milestone_dropdown(['No milestone']) + update_issues + + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).not_to have_content 'First Release' + expect(find("#issue_#{issue2.id}")).to have_content 'feature' + expect(find("#issue_#{issue2.id}")).not_to have_content 'First Release' + end + end + end + + context 'toggling checked issues' do + before do + issue1.labels << bug + enable_bulk_update + end + + it do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + + check_issue issue1 + open_labels_dropdown ['feature'] + uncheck_issue issue1 + check_issue issue1 + update_issues + sleep 1 # needed + + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'feature' + end + end + + context 'mark previously toggled label' do + before do + enable_bulk_update + end + + it do + open_labels_dropdown ['feature'] + + check_issue issue1 + + update_issues + + expect(find("#issue_#{issue1.id}")).to have_content 'feature' + end + end + + # Special case https://gitlab.com/gitlab-org/gitlab-foss/issues/24877 + context 'unmarking common label' do + before do + issue1.labels << bug + issue1.labels << feature + issue2.labels << bug + + enable_bulk_update + end + + it 'applies label from filtered results' do + check 'Select all' + + within('aside[aria-label="Bulk update"]') do + click_button 'Select labels' + wait_for_requests + + expect(page).to have_link 'bug', class: 'is-active' + expect(page).to have_link 'feature', class: 'is-indeterminate' + + click_link 'bug' + fill_in 'Search', with: 'wontfix' + click_link 'wontfix' + end + + update_issues + + first_issue = find("#issue_#{issue1.id}") + expect(first_issue).not_to have_content 'bug' + expect(first_issue).to have_content 'feature' + expect(first_issue).to have_content 'wontfix' + + second_issue = find("#issue_#{issue2.id}") + expect(second_issue).not_to have_content 'bug' + expect(second_issue).not_to have_content 'feature' + expect(second_issue).to have_content 'wontfix' + end + end + end + + context 'as a guest' do + before do + sign_in user + + visit project_issues_path(project) + end + + context 'cannot bulk assign labels' do + it do + expect(page).not_to have_button 'Edit issues' + expect(page).not_to have_unchecked_field 'Select all' + expect(page).not_to have_unchecked_field issue1.title + end + end + end + + def open_milestone_dropdown(items = []) + click_button 'Select milestone' + wait_for_requests + items.map do |item| + click_link item + end + end + + def open_labels_dropdown(items = [], unmark = false) + within('aside[aria-label="Bulk update"]') do + click_button 'Select labels' + wait_for_requests + items.map do |item| + click_link item + end + + if unmark + items.map do |item| + # Make sure we are unmarking the item no matter the state it has currently + click_link item until find('a', text: item)[:class].include? 'label-item' + end + end + end + end + + def unmark_labels_in_dropdown(items = []) + open_labels_dropdown(items, true) + end + + def check_issue(issue, uncheck = false) + if uncheck + uncheck issue.title + else + check issue.title + end + end + + def uncheck_issue(issue) + check_issue(issue, true) + end + + def update_issues + click_button 'Update all' + wait_for_requests + end + + def enable_bulk_update + visit project_issues_path(project) + wait_for_requests + click_button 'Edit issues' + end + + def disable_bulk_update + click_button 'Cancel' + end +end diff --git a/spec/features/issues/user_bulk_edits_issues_spec.rb b/spec/features/issues/user_bulk_edits_issues_spec.rb new file mode 100644 index 00000000000..e34c16e27ba --- /dev/null +++ b/spec/features/issues/user_bulk_edits_issues_spec.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Multiple issue updating from issues#index', :js do + let!(:project) { create(:project) } + let!(:issue) { create(:issue, project: project) } + let!(:user) { create(:user)} + + before do + project.add_maintainer(user) + sign_in(user) + end + + context 'status' do + it 'sets to closed' do + visit project_issues_path(project) + + click_button 'Edit issues' + check 'Select all' + click_button 'Select status' + click_link 'Closed' + + click_update_issues_button + expect(page).to have_selector('.issue', count: 0) + end + + it 'sets to open' do + create_closed + visit project_issues_path(project, state: 'closed') + + click_button 'Edit issues' + check 'Select all' + click_button 'Select status' + click_link 'Open' + + click_update_issues_button + expect(page).to have_selector('.issue', count: 0) + end + end + + context 'assignee' do + it 'updates to current user' do + visit project_issues_path(project) + + click_button 'Edit issues' + check 'Select all' + click_update_assignee_button + click_link user.username + + click_update_issues_button + + page.within('.issue .controls') do + expect(find('.author-link')['href']).to have_content(user.website_url) + end + end + + it 'updates to unassigned' do + create_assigned + visit project_issues_path(project) + + expect(find('.issue:first-of-type')).to have_link "Assigned to #{user.name}" + + click_button 'Edit issues' + check 'Select all' + click_update_assignee_button + click_link 'Unassigned' + click_update_issues_button + + expect(find('.issue:first-of-type')).not_to have_link "Assigned to #{user.name}" + end + end + + context 'milestone' do + let!(:milestone) { create(:milestone, project: project) } + + it 'updates milestone' do + visit project_issues_path(project) + + click_button 'Edit issues' + check 'Select all' + click_button 'Select milestone' + click_link milestone.title + click_update_issues_button + + expect(page.find('.issue')).to have_content milestone.title + end + + it 'sets to no milestone' do + create_with_milestone + visit project_issues_path(project) + + wait_for_requests + + expect(find('.issue:first-of-type')).to have_text milestone.title + + click_button 'Edit issues' + check 'Select all' + click_button 'Select milestone' + click_link 'No milestone' + click_update_issues_button + + expect(find('.issue:first-of-type')).not_to have_text milestone.title + end + end + + def create_closed + create(:issue, project: project, state: :closed) + end + + def create_assigned + create(:issue, project: project, assignees: [user]) + end + + def create_with_milestone + create(:issue, project: project, milestone: milestone) + end + + def click_update_assignee_button + click_button 'Select assignee' + wait_for_requests + end + + def click_update_issues_button + click_button 'Update all' + wait_for_requests + end +end diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb index 1bbb96ff479..cb4a5a32762 100644 --- a/spec/features/issues/user_edits_issue_spec.rb +++ b/spec/features/issues/user_edits_issue_spec.rb @@ -406,6 +406,12 @@ RSpec.describe "Issues > User edits issue", :js do end context 'update due date' do + before do + # Due date widget uses GraphQL and needs to wait for requests to come back + # The date picker won't be rendered before requests complete + wait_for_requests + end + it 'adds due date to issue' do date = Date.today.at_beginning_of_month + 2.days @@ -417,7 +423,7 @@ RSpec.describe "Issues > User edits issue", :js do wait_for_requests - expect(find('[data-testid="sidebar-duedate-value"]').text).to have_content date.strftime('%b %-d, %Y') + expect(find('[data-testid="sidebar-date-value"]').text).to have_content date.strftime('%b %-d, %Y') end end diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb index e862f7030c0..bbb7e8a028d 100644 --- a/spec/features/issues/user_interacts_with_awards_spec.rb +++ b/spec/features/issues/user_interacts_with_awards_spec.rb @@ -5,10 +5,6 @@ require 'spec_helper' RSpec.describe 'User interacts with awards' do let(:user) { create(:user) } - before do - stub_feature_flags(improved_emoji_picker: false) - end - describe 'User interacts with awards in an issue', :js do let(:issue) { create(:issue, project: project)} let(:project) { create(:project) } @@ -55,29 +51,24 @@ RSpec.describe 'User interacts with awards' do it 'toggles a custom award emoji' do page.within('.awards') do - page.find('.js-add-award').click + page.find('.add-reaction-button').click end - page.find('.emoji-menu.is-visible') - - expect(page).to have_selector('.js-emoji-menu-search') - expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true) - - page.within('.emoji-menu-content') do - emoji_button = page.first('.js-emoji-btn') + page.within('.emoji-picker') do + emoji_button = page.first('gl-emoji[data-name="8ball"]') emoji_button.hover emoji_button.click end 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[title='You']") + expect(page).to have_selector('[data-testid="award-button"]') + expect(page.find('[data-testid="award-button"].is-active .js-counter')).to have_content('1') + expect(page).to have_css('[data-testid="award-button"].is-active[title="You"]') expect do - page.find('.js-emoji-btn.active').click + page.find('[data-testid="award-button"].is-active').click wait_for_requests - end.to change { page.all('.award-control.js-emoji-btn').size }.from(3).to(2) + end.to change { page.all('[data-testid="award-button"]').size }.from(3).to(2) end end @@ -212,31 +203,25 @@ RSpec.describe 'User interacts with awards' do end it 'adds award to issue' do - first('.js-emoji-btn').click + first('[data-testid="award-button"]').click - expect(page).to have_selector('.js-emoji-btn.active') - expect(first('.js-emoji-btn')).to have_content '1' + expect(page).to have_selector('[data-testid="award-button"].is-active') + expect(first('[data-testid="award-button"]')).to have_content '1' visit project_issue_path(project, issue) - expect(first('.js-emoji-btn')).to have_content '1' + expect(first('[data-testid="award-button"]')).to have_content '1' end it 'removes award from issue' do - first('.js-emoji-btn').click - find('.js-emoji-btn.active').click + first('[data-testid="award-button"]').click + find('[data-testid="award-button"].is-active').click - expect(first('.js-emoji-btn')).to have_content '0' + expect(first('[data-testid="award-button"]')).to have_content '0' visit project_issue_path(project, issue) - expect(first('.js-emoji-btn')).to have_content '0' - end - - it 'only has one menu on the page' do - first('.js-add-award').click - - expect(page).to have_selector('.emoji-menu', count: 1) + expect(first('[data-testid="award-button"]')).to have_content '0' end end @@ -311,7 +296,7 @@ RSpec.describe 'User interacts with awards' do end context 'execute /award quick action' do - it 'toggles the emoji award on noteable', :js do + xit 'toggles the emoji award on noteable', :js do execute_quick_action('/award :100:') expect(find(noteable_award_counter)).to have_text("1") @@ -330,7 +315,7 @@ RSpec.describe 'User interacts with awards' do end it 'has disabled emoji button' do - expect(first('.award-control')[:class]).to have_text('disabled') + expect(first('[data-testid="award-button"]')[:class]).to have_text('disabled') end end @@ -356,7 +341,7 @@ RSpec.describe 'User interacts with awards' do end def noteable_award_counter - ".awards .active" + ".awards .is-active" end def toggle_smiley_emoji(status) diff --git a/spec/features/issues/user_toggles_subscription_spec.rb b/spec/features/issues/user_toggles_subscription_spec.rb index d91c187c840..35f4b415463 100644 --- a/spec/features/issues/user_toggles_subscription_spec.rb +++ b/spec/features/issues/user_toggles_subscription_spec.rb @@ -32,8 +32,8 @@ RSpec.describe "User toggles subscription", :js do let(:project) { create(:project_empty_repo, :public, emails_disabled: true) } it 'is disabled' do - expect(page).to have_content('Notifications have been disabled by the project or group owner') - expect(page).not_to have_selector('[data-testid="subscription-toggle"]') + expect(page).to have_content('Disabled by project owner') + expect(page).to have_button('Notifications', class: 'is-disabled') end end end diff --git a/spec/features/markdown/copy_as_gfm_spec.rb b/spec/features/markdown/copy_as_gfm_spec.rb index c9dc764f93b..c700f878df6 100644 --- a/spec/features/markdown/copy_as_gfm_spec.rb +++ b/spec/features/markdown/copy_as_gfm_spec.rb @@ -426,8 +426,8 @@ RSpec.describe 'Copy as GFM', :js do html = <<~HTML
-
-
+
+
Suggested change - passed') end end diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb index 0a373b0d51a..4a25e28a14e 100644 --- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb +++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb @@ -33,14 +33,14 @@ RSpec.describe 'Issue prioritization' do sign_in user visit project_issues_path(project, sort: 'label_priority') + wait_for_requests + # Ensure we are indicating that issues are sorted by priority - expect(page).to have_selector('.dropdown', text: 'Label priority') + expect(page).to have_button 'Label priority' - page.within('.issues-holder') do - issue_titles = all('.issues-list .issue-title-text').map(&:text) + issue_titles = all('.issues-list .issue-title-text').map(&:text) - expect(issue_titles).to eq(%w(issue_4 issue_3 issue_5 issue_2 issue_1)) - end + expect(issue_titles).to eq(%w(issue_4 issue_3 issue_5 issue_2 issue_1)) end end @@ -72,15 +72,15 @@ RSpec.describe 'Issue prioritization' do sign_in user visit project_issues_path(project, sort: 'label_priority') - expect(page).to have_selector('.dropdown', text: 'Label priority') + wait_for_requests + + expect(page).to have_button 'Label priority' - page.within('.issues-holder') do - issue_titles = all('.issues-list .issue-title-text').map(&:text) + issue_titles = all('.issues-list .issue-title-text').map(&:text) - expect(issue_titles[0..1]).to contain_exactly('issue_5', 'issue_8') - expect(issue_titles[2..4]).to contain_exactly('issue_1', 'issue_3', 'issue_7') - expect(issue_titles[5..-1]).to eq(%w(issue_2 issue_4 issue_6)) - end + expect(issue_titles[0..1]).to contain_exactly('issue_5', 'issue_8') + expect(issue_titles[2..4]).to contain_exactly('issue_1', 'issue_3', 'issue_7') + expect(issue_titles[5..-1]).to eq(%w(issue_2 issue_4 issue_6)) end end end diff --git a/spec/features/projects/labels/user_sees_links_to_issuables_spec.rb b/spec/features/projects/labels/user_sees_links_to_issuables_spec.rb index 11aa53fd963..6f98883a412 100644 --- a/spec/features/projects/labels/user_sees_links_to_issuables_spec.rb +++ b/spec/features/projects/labels/user_sees_links_to_issuables_spec.rb @@ -51,6 +51,7 @@ RSpec.describe 'Projects > Labels > User sees links to issuables' do context 'with a group label' do let_it_be(:group) { create(:group) } + let(:label) { create(:group_label, group: group, title: 'bug') } context 'when merge requests and issues are enabled for the project' do diff --git a/spec/features/projects/labels/user_views_labels_spec.rb b/spec/features/projects/labels/user_views_labels_spec.rb index da8520ca8fb..7a6942b6259 100644 --- a/spec/features/projects/labels/user_views_labels_spec.rb +++ b/spec/features/projects/labels/user_views_labels_spec.rb @@ -5,6 +5,7 @@ require "spec_helper" RSpec.describe "User views labels" do let_it_be(:project) { create(:project_empty_repo, :public) } let_it_be(:user) { create(:user) } + let(:label_titles) { %w[bug enhancement feature] } let!(:prioritized_label) { create(:label, project: project, title: 'prioritized-label-name', priority: 1) } diff --git a/spec/features/projects/members/invite_group_spec.rb b/spec/features/projects/members/invite_group_spec.rb index 83ba2533a73..4caf3e947c7 100644 --- a/spec/features/projects/members/invite_group_spec.rb +++ b/spec/features/projects/members/invite_group_spec.rb @@ -6,25 +6,48 @@ RSpec.describe 'Project > Members > Invite group', :js do include Select2Helper include ActionView::Helpers::DateHelper include Spec::Support::Helpers::Features::MembersHelpers + include Spec::Support::Helpers::Features::InviteMembersModalHelper let(:maintainer) { create(:user) } - before do - stub_feature_flags(invite_members_group_modal: false) + using RSpec::Parameterized::TableSyntax + + where(:invite_members_group_modal_enabled, :expected_invite_group_selector) do + true | 'button[data-qa-selector="invite_a_group_button"]' + false | '#invite-group-tab' + end + + with_them do + before do + stub_feature_flags(invite_members_group_modal: invite_members_group_modal_enabled) + end + + it 'displays either the invite group button or the form with tabs based on the feature flag' do + project = create(:project, namespace: create(:group)) + + project.add_maintainer(maintainer) + sign_in(maintainer) + + visit project_project_members_path(project) + + expect(page).to have_selector(expected_invite_group_selector) + end end describe 'Share with group lock' do + let(:invite_group_selector) { 'button[data-qa-selector="invite_a_group_button"]' } + shared_examples 'the project can be shared with groups' do - it 'the "Invite group" tab exists' do + it 'the "Invite a group" button exists' do visit project_project_members_path(project) - expect(page).to have_selector('#invite-group-tab') + expect(page).to have_selector(invite_group_selector) end end shared_examples 'the project cannot be shared with groups' do - it 'the "Invite group" tab does not exist' do + it 'the "Invite a group" button does not exist' do visit project_project_members_path(project) - expect(page).not_to have_selector('#invite-group-tab') + expect(page).not_to have_selector(invite_group_selector) end end @@ -41,7 +64,9 @@ RSpec.describe 'Project > Members > Invite group', :js do context 'when the group has "Share with group lock" disabled' do it_behaves_like 'the project can be shared with groups' - it 'the project can be shared with another group' do + it 'the project can be shared with another group when the feature flag invite_members_group_modal is disabled' do + stub_feature_flags(invite_members_group_modal: false) + visit project_project_members_path(project) expect(page).not_to have_link 'Groups' @@ -56,6 +81,22 @@ RSpec.describe 'Project > Members > Invite group', :js do expect(members_table).to have_content(group_to_share_with.name) end + + it 'the project can be shared with another group when the feature flag invite_members_group_modal is enabled' do + stub_feature_flags(invite_members_group_modal: true) + + visit project_project_members_path(project) + + expect(page).not_to have_link 'Groups' + + invite_group(group_to_share_with.name) + + visit project_project_members_path(project) + + click_link 'Groups' + + expect(members_table).to have_content(group_to_share_with.name) + end end context 'when the group has "Share with group lock" enabled' do @@ -127,13 +168,7 @@ RSpec.describe 'Project > Members > Invite group', :js do visit project_project_members_path(project) - click_on 'invite-group-tab' - - select2 group.id, from: '#link_group_id' - - fill_in 'expires_at_groups', with: 5.days.from_now.strftime('%Y-%m-%d') - click_on 'invite-group-tab' - find('.btn-confirm').click + invite_group(group.name, role: 'Guest', expires_at: 5.days.from_now) end it 'the group link shows the expiration time with a warning class' do @@ -149,29 +184,23 @@ RSpec.describe 'Project > Members > Invite group', :js do context 'with multiple groups to choose from' do let(:project) { create(:project) } - before do + it 'includes multiple groups' do project.add_maintainer(maintainer) sign_in(maintainer) - create(:group).add_owner(maintainer) - create(:group).add_owner(maintainer) + group1 = create(:group) + group1.add_owner(maintainer) + group2 = create(:group) + group2.add_owner(maintainer) visit project_project_members_path(project) - click_link 'Invite group' + click_on 'Invite a group' + click_on 'Select a group' + wait_for_requests - find('.ajax-groups-select.select2-container') - - execute_script 'GROUP_SELECT_PER_PAGE = 1;' - open_select2 '#link_group_id' - end - - it 'infinitely scrolls' do - expect(find('.select2-drop .select2-results')).to have_selector('.select2-result', count: 1) - - scroll_select2_to_bottom('.select2-drop .select2-results:visible') - - expect(find('.select2-drop .select2-results')).to have_selector('.select2-result', count: 2) + expect(page).to have_button(group1.name) + expect(page).to have_button(group2.name) end end @@ -188,16 +217,19 @@ RSpec.describe 'Project > Members > Invite group', :js do group_to_share_with.add_maintainer(maintainer) end - it 'the groups dropdown does not show ancestors' do + # This behavior should be changed to exclude the ancestor and project + # group from the options once issue is fixed for the modal: + # https://gitlab.com/gitlab-org/gitlab/-/issues/329835 + it 'the groups dropdown does show ancestors and the project group' do visit project_project_members_path(project) - click_on 'invite-group-tab' - click_link 'Search for a group' + click_on 'Invite a group' + click_on 'Select a group' + wait_for_requests - page.within '.select2-drop' do - expect(page).to have_content(group_to_share_with.name) - expect(page).not_to have_content(group.name) - end + expect(page).to have_button(group_to_share_with.name) + expect(page).to have_button(group.name) + expect(page).to have_button(nested_group.name) end end end diff --git a/spec/features/projects/members/list_spec.rb b/spec/features/projects/members/list_spec.rb index 384b8ae9929..f1fc579bb8a 100644 --- a/spec/features/projects/members/list_spec.rb +++ b/spec/features/projects/members/list_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe 'Project members list', :js do - include Select2Helper include Spec::Support::Helpers::Features::MembersHelpers + include Spec::Support::Helpers::Features::InviteMembersModalHelper let(:user1) { create(:user, name: 'John Doe') } let(:user2) { create(:user, name: 'Mary Jane') } @@ -12,8 +12,6 @@ RSpec.describe 'Project members list', :js do let(:project) { create(:project, :internal, namespace: group) } before do - stub_feature_flags(invite_members_group_modal: true) - sign_in(user1) group.add_owner(user1) end @@ -52,7 +50,7 @@ RSpec.describe 'Project members list', :js do it 'add user to project' do visit_members_page - add_user(user2.name, 'Reporter') + invite_member(user2.name, role: 'Reporter') page.within find_member_row(user2) do expect(page).to have_button('Reporter') @@ -100,7 +98,7 @@ RSpec.describe 'Project members list', :js do it 'invite user to project' do visit_members_page - add_user('test@example.com', 'Reporter') + invite_member('test@example.com', role: 'Reporter') click_link 'Invited' @@ -171,25 +169,6 @@ RSpec.describe 'Project members list', :js do private - def add_user(id, role) - click_on 'Invite members' - - page.within '#invite-members-modal' do - fill_in 'Select members or type email addresses', with: id - - wait_for_requests - click_button id - - click_button 'Guest' - wait_for_requests - click_button role - - click_button 'Invite' - end - - page.refresh - end - def visit_members_page visit project_project_members_path(project) end diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb index d22097a2f6f..c1b14cf60e7 100644 --- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb +++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb @@ -3,12 +3,13 @@ require 'spec_helper' RSpec.describe 'Projects > Members > Maintainer adds member with expiration date', :js do - include Select2Helper include ActiveSupport::Testing::TimeHelpers include Spec::Support::Helpers::Features::MembersHelpers + include Spec::Support::Helpers::Features::InviteMembersModalHelper let_it_be(:maintainer) { create(:user) } let_it_be(:project) { create(:project) } + let(:new_member) { create(:user) } before do @@ -19,18 +20,9 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date end it 'expiration date is displayed in the members list' do - stub_feature_flags(invite_members_group_modal: false) - visit project_project_members_path(project) - page.within '.invite-users-form' do - select2(new_member.id, from: '#user_ids', multiple: true) - - fill_in 'expires_at', with: 5.days.from_now.to_date - find_field('expires_at').native.send_keys :enter - - click_on 'Invite' - end + invite_member(new_member.name, role: 'Guest', expires_at: 5.days.from_now.to_date) page.within find_member_row(new_member) do expect(page).to have_content(/in \d days/) diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb index 93bbabcc3f8..335ae6794b7 100644 --- a/spec/features/projects/merge_request_button_spec.rb +++ b/spec/features/projects/merge_request_button_spec.rb @@ -7,6 +7,7 @@ RSpec.describe 'Merge Request button' do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :public, :repository) } + let(:forked_project) { fork_project(project, user, repository: true) } shared_examples 'Merge request button only shown when allowed' do diff --git a/spec/features/projects/milestones/gfm_autocomplete_spec.rb b/spec/features/projects/milestones/gfm_autocomplete_spec.rb new file mode 100644 index 00000000000..547a5d11dec --- /dev/null +++ b/spec/features/projects/milestones/gfm_autocomplete_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'GFM autocomplete', :js do + let_it_be(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') } + let_it_be(:group) { create(:group, name: 'Ancestor') } + let_it_be(:project) { create(:project, :repository, group: group) } + let_it_be(:issue) { create(:issue, project: project, assignees: [user], title: 'My special issue') } + let_it_be(:label) { create(:label, project: project, title: 'special+') } + let_it_be(:milestone) { create(:milestone, resource_parent: project, title: "project milestone") } + let_it_be(:merge_request) { create(:merge_request, source_project: project) } + + shared_examples 'displays autocomplete menu for all entities' do + it 'autocompletes all available entities' do + fill_in 'Description', with: User.reference_prefix + wait_for_requests + expect(find_autocomplete_menu).to be_visible + expect_autocomplete_entry(user.name) + + fill_in 'Description', with: Label.reference_prefix + wait_for_requests + expect(find_autocomplete_menu).to be_visible + expect_autocomplete_entry(label.title) + + fill_in 'Description', with: Milestone.reference_prefix + wait_for_requests + expect(find_autocomplete_menu).to be_visible + expect_autocomplete_entry(milestone.title) + + fill_in 'Description', with: Issue.reference_prefix + wait_for_requests + expect(find_autocomplete_menu).to be_visible + expect_autocomplete_entry(issue.title) + + fill_in 'Description', with: MergeRequest.reference_prefix + wait_for_requests + expect(find_autocomplete_menu).to be_visible + expect_autocomplete_entry(merge_request.title) + end + end + + before_all do + group.add_maintainer(user) + end + + describe 'new milestone page' do + before do + sign_in(user) + visit new_project_milestone_path(project) + + wait_for_requests + end + + it_behaves_like 'displays autocomplete menu for all entities' + end + + describe 'update milestone page' do + before do + sign_in(user) + visit edit_project_milestone_path(project, milestone) + + wait_for_requests + end + + it_behaves_like 'displays autocomplete menu for all entities' + end + + private + + def find_autocomplete_menu + find('.atwho-view ul', visible: true) + end + + def expect_autocomplete_entry(entry) + page.within('.atwho-container') do + expect(page).to have_content(entry) + end + end +end diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb index 7dc3ee63669..ee5bf99fd75 100644 --- a/spec/features/projects/navbar_spec.rb +++ b/spec/features/projects/navbar_spec.rb @@ -8,62 +8,168 @@ RSpec.describe 'Project navbar' do include_context 'project navbar structure' - let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :repository) } - before do - insert_package_nav(_('Operations')) - insert_infrastructure_registry_nav - stub_config(registry: { enabled: false }) + let(:user) { project.owner } - project.add_maintainer(user) + before do sign_in(user) end - it_behaves_like 'verified navigation bar' do + context 'when sidebar refactor feature flag is disabled' do before do - visit project_path(project) + stub_feature_flags(sidebar_refactor: false) + insert_package_nav(_('Operations')) + insert_infrastructure_registry_nav + + insert_after_sub_nav_item( + _('Boards'), + within: _('Issues'), + new_sub_nav_item_name: _('Labels') + ) + + insert_after_nav_item( + _('Snippets'), + new_nav_item: { + nav_item: _('Members'), + nav_sub_items: [] + } + ) + + stub_config(registry: { enabled: false }) end - end - context 'when value stream is available' do - before do - visit project_path(project) + it_behaves_like 'verified navigation bar' do + before do + visit project_path(project) + end end - it 'redirects to value stream when Analytics item is clicked' do - page.within('.sidebar-top-level-items') do - find('[data-qa-selector=analytics_anchor]').click + context 'when value stream is available' do + before do + visit project_path(project) end - wait_for_requests + it 'redirects to value stream when Analytics item is clicked' do + page.within('.sidebar-top-level-items') do + find('.shortcuts-analytics').click + end + + wait_for_requests - expect(page).to have_current_path(project_cycle_analytics_path(project)) + expect(page).to have_current_path(project_cycle_analytics_path(project)) + end end - end - context 'when pages are available' do - before do - stub_config(pages: { enabled: true }) + context 'when pages are available' do + before do + stub_config(pages: { enabled: true }) - insert_after_sub_nav_item( - _('Operations'), - within: _('Settings'), - new_sub_nav_item_name: _('Pages') - ) + insert_after_sub_nav_item( + _('Operations'), + within: _('Settings'), + new_sub_nav_item_name: _('Pages') + ) - visit project_path(project) + visit project_path(project) + end + + it_behaves_like 'verified navigation bar' end - it_behaves_like 'verified navigation bar' + context 'when container registry is available' do + before do + stub_config(registry: { enabled: true }) + + insert_container_nav + + visit project_path(project) + end + + it_behaves_like 'verified navigation bar' + end end - context 'when container registry is available' do + context 'when sidebar refactor feature flag is enabled' do + let(:monitor_nav_item) do + { + nav_item: _('Monitor'), + nav_sub_items: monitor_menu_items + } + end + + let(:monitor_menu_items) do + [ + _('Metrics'), + _('Logs'), + _('Tracing'), + _('Error Tracking'), + _('Alerts'), + _('Incidents'), + _('Product Analytics') + ] + end + + let(:project_information_nav_item) do + { + nav_item: _('Project information'), + nav_sub_items: [ + _('Activity'), + _('Labels'), + _('Members') + ] + } + end + + let(:settings_menu_items) do + [ + _('General'), + _('Integrations'), + _('Webhooks'), + _('Access Tokens'), + _('Repository'), + _('CI/CD'), + _('Monitor') + ] + end + before do + stub_feature_flags(sidebar_refactor: true) stub_config(registry: { enabled: true }) - + insert_package_nav(_('Monitor')) + insert_infrastructure_registry_nav insert_container_nav + insert_after_sub_nav_item( + _('Monitor'), + within: _('Settings'), + new_sub_nav_item_name: _('Packages & Registries') + ) + + insert_after_nav_item( + _('Monitor'), + new_nav_item: { + nav_item: _('Infrastructure'), + nav_sub_items: [ + _('Kubernetes clusters'), + _('Serverless platform'), + _('Terraform') + ] + } + ) + + insert_after_nav_item( + _('Security & Compliance'), + new_nav_item: { + nav_item: _('Deployments'), + nav_sub_items: [ + _('Feature Flags'), + _('Environments'), + _('Releases') + ] + } + ) + visit project_path(project) end diff --git a/spec/features/projects/new_project_from_template_spec.rb b/spec/features/projects/new_project_from_template_spec.rb new file mode 100644 index 00000000000..1c8647d859a --- /dev/null +++ b/spec/features/projects/new_project_from_template_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'New project from template', :js do + let(:user) { create(:user) } + + before do + sign_in(user) + + visit new_project_path + end + + context 'create from template' do + before do + page.find('a[href="#create_from_template"]').click + wait_for_requests + end + + it 'shows template tabs' do + page.within('#create-from-template-pane') do + expect(page).to have_link('Built-in', href: '#built-in') + end + end + end +end diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index 7119039d5ff..a1523f9eb08 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -5,50 +5,48 @@ require 'spec_helper' RSpec.describe 'New project', :js do include Select2Helper - context 'as a user' do - let(:user) { create(:user) } + shared_examples 'combined_menu: feature flag examples' do + context 'as a user' do + let(:user) { create(:user) } - before do - sign_in(user) - end - - context 'new repo experiment', :experiment do - it 'when in control renders "project"' do - stub_experiments(new_repo: :control) + before do + sign_in(user) + end - visit new_project_path + context 'new repo experiment', :experiment do + it 'when in control renders "project"' do + stub_experiments(new_repo: :control) - find('li.header-new.dropdown').click + visit new_project_path - page.within('li.header-new.dropdown') do - expect(page).to have_selector('a', text: 'New project') - expect(page).to have_no_selector('a', text: 'New project/repository') - end + find('li.header-new.dropdown').click - expect(page).to have_selector('.blank-state-title', text: 'Create blank project') - expect(page).to have_no_selector('.blank-state-title', text: 'Create blank project/repository') - end + page.within('li.header-new.dropdown') do + expect(page).to have_selector('a', text: 'New project') + expect(page).to have_no_selector('a', text: 'New project/repository') + end - it 'when in candidate renders "project/repository"' do - stub_experiments(new_repo: :candidate) + expect(page).to have_selector('h3', text: 'Create blank project') + expect(page).to have_no_selector('h3', text: 'Create blank project/repository') + end - visit new_project_path + it 'when in candidate renders "project/repository"' do + stub_experiments(new_repo: :candidate) - find('li.header-new.dropdown').click + visit new_project_path - page.within('li.header-new.dropdown') do - expect(page).to have_selector('a', text: 'New project/repository') - end + find('li.header-new.dropdown').click - expect(page).to have_selector('.blank-state-title', text: 'Create blank project/repository') - end + page.within('li.header-new.dropdown') do + expect(page).to have_selector('a', text: 'New project/repository') + end - context 'with combined_menu feature disabled' do - before do - stub_feature_flags(combined_menu: false) + expect(page).to have_selector('h3', text: 'Create blank project/repository') end it 'when in control it renders "project" in the new projects dropdown' do + pending_on_combined_menu_flag + stub_experiments(new_repo: :control) visit new_project_path @@ -64,6 +62,8 @@ RSpec.describe 'New project', :js do end it 'when in candidate it renders "project/repository" in the new projects dropdown' do + pending_on_combined_menu_flag + stub_experiments(new_repo: :candidate) visit new_project_path @@ -76,337 +76,373 @@ RSpec.describe 'New project', :js do end end end - end - it 'shows a message if multiple levels are restricted' do - Gitlab::CurrentSettings.update!( - restricted_visibility_levels: [Gitlab::VisibilityLevel::PRIVATE, Gitlab::VisibilityLevel::INTERNAL] - ) + it 'shows a message if multiple levels are restricted' do + Gitlab::CurrentSettings.update!( + restricted_visibility_levels: [Gitlab::VisibilityLevel::PRIVATE, Gitlab::VisibilityLevel::INTERNAL] + ) - visit new_project_path - find('[data-qa-selector="blank_project_link"]').click - - expect(page).to have_content 'Other visibility settings have been disabled by the administrator.' - end + visit new_project_path + find('[data-qa-selector="blank_project_link"]').click - it 'shows a message if all levels are restricted' do - Gitlab::CurrentSettings.update!( - restricted_visibility_levels: Gitlab::VisibilityLevel.values - ) + expect(page).to have_content 'Other visibility settings have been disabled by the administrator.' + end - visit new_project_path - find('[data-qa-selector="blank_project_link"]').click + it 'shows a message if all levels are restricted' do + Gitlab::CurrentSettings.update!( + restricted_visibility_levels: Gitlab::VisibilityLevel.values + ) - expect(page).to have_content 'Visibility settings have been disabled by the administrator.' - end - end - - context 'as an admin' do - let(:user) { create(:admin) } + visit new_project_path + find('[data-qa-selector="blank_project_link"]').click - before do - sign_in(user) + expect(page).to have_content 'Visibility settings have been disabled by the administrator.' + end end - it 'shows "New project" page', :js do - visit new_project_path - find('[data-qa-selector="blank_project_link"]').click - - expect(page).to have_content('Project name') - expect(page).to have_content('Project URL') - expect(page).to have_content('Project slug') - - click_link('New project') - find('[data-qa-selector="import_project_link"]').click + context 'as an admin' do + let(:user) { create(:admin) } - expect(page).to have_link('GitHub') - expect(page).to have_link('Bitbucket') - expect(page).to have_link('GitLab.com') - expect(page).to have_button('Repo by URL') - expect(page).to have_link('GitLab export') - end - - describe 'manifest import option' do before do + sign_in(user) + end + + it 'shows "New project" page', :js do visit new_project_path + find('[data-qa-selector="blank_project_link"]').click - find('[data-qa-selector="import_project_link"]').click - end + expect(page).to have_content('Project name') + expect(page).to have_content('Project URL') + expect(page).to have_content('Project slug') - it { expect(page).to have_link('Manifest file') } - end + click_link('New project') + find('[data-qa-selector="import_project_link"]').click - context 'Visibility level selector', :js do - Gitlab::VisibilityLevel.options.each do |key, level| - it "sets selector to #{key}" do - stub_application_setting(default_project_visibility: level) + expect(page).to have_link('GitHub') + expect(page).to have_link('Bitbucket') + expect(page).to have_link('GitLab.com') + expect(page).to have_button('Repo by URL') + expect(page).to have_link('GitLab export') + end + describe 'manifest import option' do + before do visit new_project_path - find('[data-qa-selector="blank_project_link"]').click - page.within('#blank-project-pane') do - expect(find_field("project_visibility_level_#{level}")).to be_checked - end - end - it "saves visibility level #{level} on validation error" do - visit new_project_path - find('[data-qa-selector="blank_project_link"]').click + find('[data-qa-selector="import_project_link"]').click + end - choose(key) - click_button('Create project') - page.within('#blank-project-pane') do - expect(find_field("project_visibility_level_#{level}")).to be_checked - end + it 'has Manifest file' do + expect(page).to have_link('Manifest file') end end - context 'when group visibility is private but default is internal' do - let_it_be(:group) { create(:group, visibility_level: Gitlab::VisibilityLevel::PRIVATE) } + context 'Visibility level selector', :js do + Gitlab::VisibilityLevel.options.each do |key, level| + it "sets selector to #{key}" do + stub_application_setting(default_project_visibility: level) - before do - stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL) - end + visit new_project_path + find('[data-qa-selector="blank_project_link"]').click + page.within('#blank-project-pane') do + expect(find_field("project_visibility_level_#{level}")).to be_checked + end + end - context 'when admin mode is enabled', :enable_admin_mode do - it 'has private selected' do - visit new_project_path(namespace_id: group.id) + it "saves visibility level #{level} on validation error" do + visit new_project_path find('[data-qa-selector="blank_project_link"]').click + choose(key) + click_button('Create project') page.within('#blank-project-pane') do - expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked + expect(find_field("project_visibility_level_#{level}")).to be_checked end end end - context 'when admin mode is disabled' do - it 'is not allowed' do - visit new_project_path(namespace_id: group.id) + context 'when group visibility is private but default is internal' do + let_it_be(:group) { create(:group, visibility_level: Gitlab::VisibilityLevel::PRIVATE) } - expect(page).to have_content('Not Found') + before do + stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL) end - end - end - context 'when group visibility is public but user requests private' do - let_it_be(:group) { create(:group, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + context 'when admin mode is enabled', :enable_admin_mode do + it 'has private selected' do + visit new_project_path(namespace_id: group.id) + find('[data-qa-selector="blank_project_link"]').click - before do - stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL) - end + page.within('#blank-project-pane') do + expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked + end + end + end - context 'when admin mode is enabled', :enable_admin_mode do - it 'has private selected' do - visit new_project_path(namespace_id: group.id, project: { visibility_level: Gitlab::VisibilityLevel::PRIVATE }) - find('[data-qa-selector="blank_project_link"]').click + context 'when admin mode is disabled' do + it 'is not allowed' do + visit new_project_path(namespace_id: group.id) - page.within('#blank-project-pane') do - expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked + expect(page).to have_content('Not Found') end end end - context 'when admin mode is disabled' do - it 'is not allowed' do - visit new_project_path(namespace_id: group.id, project: { visibility_level: Gitlab::VisibilityLevel::PRIVATE }) + context 'when group visibility is public but user requests private' do + let_it_be(:group) { create(:group, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } - expect(page).to have_content('Not Found') + before do + stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL) end - end - end - end - context 'Readme selector' do - it 'shows the initialize with Readme checkbox on "Blank project" tab' do - visit new_project_path - find('[data-qa-selector="blank_project_link"]').click + context 'when admin mode is enabled', :enable_admin_mode do + it 'has private selected' do + visit new_project_path(namespace_id: group.id, project: { visibility_level: Gitlab::VisibilityLevel::PRIVATE }) + find('[data-qa-selector="blank_project_link"]').click - expect(page).to have_css('input#project_initialize_with_readme') - expect(page).to have_content('Initialize repository with a README') - end + page.within('#blank-project-pane') do + expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked + end + end + end - it 'does not show the initialize with Readme checkbox on "Create from template" tab' do - visit new_project_path - find('[data-qa-selector="create_from_template_link"]').click - first('.choose-template').click + context 'when admin mode is disabled' do + it 'is not allowed' do + visit new_project_path(namespace_id: group.id, project: { visibility_level: Gitlab::VisibilityLevel::PRIVATE }) - page.within '.project-fields-form' do - expect(page).not_to have_css('input#project_initialize_with_readme') - expect(page).not_to have_content('Initialize repository with a README') + expect(page).to have_content('Not Found') + end + end end end - it 'does not show the initialize with Readme checkbox on "Import project" tab' do - visit new_project_path - find('[data-qa-selector="import_project_link"]').click - first('.js-import-git-toggle-button').click + context 'Readme selector' do + it 'shows the initialize with Readme checkbox on "Blank project" tab' do + visit new_project_path + find('[data-qa-selector="blank_project_link"]').click - page.within '.toggle-import-form' do - expect(page).not_to have_css('input#project_initialize_with_readme') - expect(page).not_to have_content('Initialize repository with a README') + expect(page).to have_css('input#project_initialize_with_readme') + expect(page).to have_content('Initialize repository with a README') end - end - end - context 'Namespace selector' do - context 'with user namespace' do - before do + it 'does not show the initialize with Readme checkbox on "Create from template" tab' do visit new_project_path - find('[data-qa-selector="blank_project_link"]').click + find('[data-qa-selector="create_from_template_link"]').click + first('.choose-template').click + + page.within '.project-fields-form' do + expect(page).not_to have_css('input#project_initialize_with_readme') + expect(page).not_to have_content('Initialize repository with a README') + end end - it 'selects the user namespace' do - page.within('#blank-project-pane') do - expect(page).to have_select('project[namespace_id]', visible: false, selected: user.username) + it 'does not show the initialize with Readme checkbox on "Import project" tab' do + visit new_project_path + find('[data-qa-selector="import_project_link"]').click + first('.js-import-git-toggle-button').click + + page.within '#import-project-pane' do + expect(page).not_to have_css('input#project_initialize_with_readme') + expect(page).not_to have_content('Initialize repository with a README') end end end - context 'with group namespace' do - let(:group) { create(:group, :private) } + context 'Namespace selector' do + context 'with user namespace' do + before do + visit new_project_path + find('[data-qa-selector="blank_project_link"]').click + end - before do - group.add_owner(user) - visit new_project_path(namespace_id: group.id) - find('[data-qa-selector="blank_project_link"]').click + it 'selects the user namespace' do + page.within('#blank-project-pane') do + expect(page).to have_select('project[namespace_id]', visible: false, selected: user.username) + end + end end - it 'selects the group namespace' do - page.within('#blank-project-pane') do - expect(page).to have_select('project[namespace_id]', visible: false, selected: group.name) + context 'with group namespace' do + let(:group) { create(:group, :private) } + + before do + group.add_owner(user) + visit new_project_path(namespace_id: group.id) + find('[data-qa-selector="blank_project_link"]').click + end + + it 'selects the group namespace' do + page.within('#blank-project-pane') do + expect(page).to have_select('project[namespace_id]', visible: false, selected: group.name) + end end end - end - context 'with subgroup namespace' do - let(:group) { create(:group) } - let(:subgroup) { create(:group, parent: group) } + context 'with subgroup namespace' do + let(:group) { create(:group) } + let(:subgroup) { create(:group, parent: group) } - before do - group.add_maintainer(user) - visit new_project_path(namespace_id: subgroup.id) - find('[data-qa-selector="blank_project_link"]').click - end + before do + group.add_maintainer(user) + visit new_project_path(namespace_id: subgroup.id) + find('[data-qa-selector="blank_project_link"]').click + end - it 'selects the group namespace' do - page.within('#blank-project-pane') do - expect(page).to have_select('project[namespace_id]', visible: false, selected: subgroup.full_path) + it 'selects the group namespace' do + page.within('#blank-project-pane') do + expect(page).to have_select('project[namespace_id]', visible: false, selected: subgroup.full_path) + end end end - end - context 'when changing namespaces dynamically', :js do - let(:public_group) { create(:group, :public) } - let(:internal_group) { create(:group, :internal) } - let(:private_group) { create(:group, :private) } + context 'when changing namespaces dynamically', :js do + let(:public_group) { create(:group, :public) } + let(:internal_group) { create(:group, :internal) } + let(:private_group) { create(:group, :private) } - before do - public_group.add_owner(user) - internal_group.add_owner(user) - private_group.add_owner(user) - visit new_project_path(namespace_id: public_group.id) - find('[data-qa-selector="blank_project_link"]').click - end + before do + public_group.add_owner(user) + internal_group.add_owner(user) + private_group.add_owner(user) + visit new_project_path(namespace_id: public_group.id) + find('[data-qa-selector="blank_project_link"]').click + end - it 'enables the correct visibility options' do - select2(user.namespace_id, from: '#project_namespace_id') - expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled - expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled - expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).not_to be_disabled - - select2(public_group.id, from: '#project_namespace_id') - expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled - expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled - expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).not_to be_disabled - - select2(internal_group.id, from: '#project_namespace_id') - expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled - expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled - expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).to be_disabled - - select2(private_group.id, from: '#project_namespace_id') - expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled - expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).to be_disabled - expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).to be_disabled + it 'enables the correct visibility options' do + select2(user.namespace_id, from: '#project_namespace_id') + expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled + expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled + expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).not_to be_disabled + + select2(public_group.id, from: '#project_namespace_id') + expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled + expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled + expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).not_to be_disabled + + select2(internal_group.id, from: '#project_namespace_id') + expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled + expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled + expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).to be_disabled + + select2(private_group.id, from: '#project_namespace_id') + expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled + expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).to be_disabled + expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).to be_disabled + end end end - end - context 'Import project options', :js do - before do - visit new_project_path - find('[data-qa-selector="import_project_link"]').click - end - - context 'from git repository url, "Repo by URL"' do + context 'Import project options', :js do before do - first('.js-import-git-toggle-button').click + visit new_project_path + find('[data-qa-selector="import_project_link"]').click end - it 'does not autocomplete sensitive git repo URL' do - autocomplete = find('#project_import_url')['autocomplete'] + context 'from git repository url, "Repo by URL"' do + before do + first('.js-import-git-toggle-button').click + end - expect(autocomplete).to eq('off') - end + it 'does not autocomplete sensitive git repo URL' do + autocomplete = find('#project_import_url')['autocomplete'] - it 'shows import instructions' do - git_import_instructions = first('.js-toggle-content') + expect(autocomplete).to eq('off') + end - expect(git_import_instructions).to be_visible - expect(git_import_instructions).to have_content 'Git repository URL' - end + it 'shows import instructions' do + git_import_instructions = first('.js-toggle-content') - it 'keeps "Import project" tab open after form validation error' do - collision_project = create(:project, name: 'test-name-collision', namespace: user.namespace) + expect(git_import_instructions).to be_visible + expect(git_import_instructions).to have_content 'Git repository URL' + end - fill_in 'project_import_url', with: collision_project.http_url_to_repo - fill_in 'project_name', with: collision_project.name + it 'reports error if repo URL does not end with .git' do + fill_in 'project_import_url', with: 'http://foo/bar' + fill_in 'project_name', with: 'import-project-without-git-suffix' + fill_in 'project_path', with: 'import-project-without-git-suffix' - click_on 'Create project' + click_button 'Create project' - expect(page).to have_css('#import-project-pane.active') - expect(page).not_to have_css('.toggle-import-form.hide') - end - end + expect(page).to have_text('Please provide a valid URL ending with .git') + end - context 'from GitHub' do - before do - first('.js-import-github').click - end + it 'keeps "Import project" tab open after form validation error' do + collision_project = create(:project, name: 'test-name-collision', namespace: user.namespace) + + fill_in 'project_import_url', with: collision_project.http_url_to_repo + fill_in 'project_name', with: collision_project.name + + click_on 'Create project' - it 'shows import instructions' do - expect(page).to have_content('Authenticate with GitHub') - expect(current_path).to eq new_import_github_path + expect(page).to have_css('#import-project-pane.active') + expect(page).not_to have_css('.toggle-import-form.hide') + end end - end - context 'from manifest file' do - before do - first('.import_manifest').click + context 'from GitHub' do + before do + first('.js-import-github').click + end + + it 'shows import instructions' do + expect(page).to have_content('Authenticate with GitHub') + expect(current_path).to eq new_import_github_path + end end - it 'shows import instructions' do - expect(page).to have_content('Manifest file import') - expect(current_path).to eq new_import_manifest_path + context 'from manifest file' do + before do + first('.import_manifest').click + end + + it 'shows import instructions' do + expect(page).to have_content('Manifest file import') + expect(current_path).to eq new_import_manifest_path + end end end - end - context 'Namespace selector' do - context 'with group with DEVELOPER_MAINTAINER_PROJECT_ACCESS project_creation_level' do - let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) } + context 'Namespace selector' do + context 'with group with DEVELOPER_MAINTAINER_PROJECT_ACCESS project_creation_level' do + let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) } - before do - group.add_developer(user) - visit new_project_path(namespace_id: group.id) - find('[data-qa-selector="blank_project_link"]').click - end + before do + group.add_developer(user) + visit new_project_path(namespace_id: group.id) + find('[data-qa-selector="blank_project_link"]').click + end - it 'selects the group namespace' do - page.within('#blank-project-pane') do - expect(page).to have_select('project[namespace_id]', visible: false, selected: group.full_path) + it 'selects the group namespace' do + page.within('#blank-project-pane') do + expect(page).to have_select('project[namespace_id]', visible: false, selected: group.full_path) + end end end end end end + + context 'with combined_menu: feature flag on' do + let(:needs_rewrite_for_combined_menu_flag_on) { true } + + before do + stub_feature_flags(combined_menu: true) + end + + it_behaves_like 'combined_menu: feature flag examples' + end + + context 'with combined_menu feature flag off' do + let(:needs_rewrite_for_combined_menu_flag_on) { false } + + before do + stub_feature_flags(combined_menu: false) + end + + it_behaves_like 'combined_menu: feature flag examples' + end + + def pending_on_combined_menu_flag + pending 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56587' if needs_rewrite_for_combined_menu_flag_on + end end diff --git a/spec/features/projects/pages/user_adds_domain_spec.rb b/spec/features/projects/pages/user_adds_domain_spec.rb index 24c9edb79e5..de9effe3dc7 100644 --- a/spec/features/projects/pages/user_adds_domain_spec.rb +++ b/spec/features/projects/pages/user_adds_domain_spec.rb @@ -5,6 +5,7 @@ RSpec.describe 'User adds pages domain', :js do include LetsEncryptHelpers let_it_be(:project) { create(:project, pages_https_only: false) } + let(:user) { create(:user) } before do diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 4a0581bb5cf..70dc0bd04e8 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -739,6 +739,7 @@ RSpec.describe 'Pipeline', :js do context 'when build requires resource', :sidekiq_inline do let_it_be(:project) { create(:project, :repository) } + let(:pipeline) { create(:ci_pipeline, project: project) } let(:resource_group) { create(:ci_resource_group, project: project) } diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index e375bc10dbf..f1672af1019 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -457,22 +457,8 @@ RSpec.describe 'Pipelines', :js do visit_project_pipelines end - it 'has artifacts' do - expect(page).to have_selector('.build-artifacts') - end - - it 'has artifacts download dropdown' do - find('.js-pipeline-dropdown-download').click - - expect(page).to have_link(with_artifacts.file_type) - end - - it 'has download attribute on download links' do - find('.js-pipeline-dropdown-download').click - expect(page).to have_selector('a', text: 'Download') - page.all('.build-artifacts a', text: 'Download').each do |link| - expect(link[:download]).to eq '' - end + it 'has artifacts dropdown' do + expect(page).to have_selector('[data-testid="pipeline-multi-actions-dropdown"]') end end @@ -488,7 +474,7 @@ RSpec.describe 'Pipelines', :js do visit_project_pipelines end - it { expect(page).not_to have_selector('.build-artifacts') } + it { expect(page).not_to have_selector('[data-testid="artifact-item"]') } end context 'without artifacts' do @@ -503,7 +489,7 @@ RSpec.describe 'Pipelines', :js do visit_project_pipelines end - it { expect(page).not_to have_selector('.build-artifacts') } + it { expect(page).not_to have_selector('[data-testid="artifact-item"]') } end context 'with trace artifact' do @@ -514,7 +500,7 @@ RSpec.describe 'Pipelines', :js do end it 'does not show trace artifact as artifacts' do - expect(page).not_to have_selector('.build-artifacts') + expect(page).not_to have_selector('[data-testid="artifact-item"]') end end end @@ -657,26 +643,28 @@ RSpec.describe 'Pipelines', :js do let(:project) { create(:project, :repository) } before do - stub_feature_flags(new_pipeline_form: false) visit new_project_pipeline_path(project) end context 'for valid commit', :js do before do click_button project.default_branch + wait_for_requests - page.within '.dropdown-menu' do - click_link 'master' - end + find('p', text: 'master').click + wait_for_requests end - context 'with gitlab-ci.yml' do + context 'with gitlab-ci.yml', :js do before do stub_ci_pipeline_to_return_yaml_file end it 'creates a new pipeline' do - expect { click_on 'Run pipeline' } + expect do + click_on 'Run pipeline' + wait_for_requests + end .to change { Ci::Pipeline.count }.by(1) expect(Ci::Pipeline.last).to be_web @@ -684,12 +672,15 @@ RSpec.describe 'Pipelines', :js do context 'when variables are specified' do it 'creates a new pipeline with variables' do - page.within '.ci-variable-row-body' do - fill_in "Input variable key", with: "key_name" - fill_in "Input variable value", with: "value" + page.within(find("[data-testid='ci-variable-row']")) do + find("[data-testid='pipeline-form-ci-variable-key']").set('key_name') + find("[data-testid='pipeline-form-ci-variable-value']").set('value') end - expect { click_on 'Run pipeline' } + expect do + click_on 'Run pipeline' + wait_for_requests + end .to change { Ci::Pipeline.count }.by(1) expect(Ci::Pipeline.last.variables.map { |var| var.slice(:key, :secret_value) }) @@ -701,19 +692,17 @@ RSpec.describe 'Pipelines', :js do context 'without gitlab-ci.yml' do before do click_on 'Run pipeline' + wait_for_requests end it { expect(page).to have_content('Missing CI config file') } it 'creates a pipeline after first request failed and a valid gitlab-ci.yml file is available when trying again' do - click_button project.default_branch - stub_ci_pipeline_to_return_yaml_file - page.within '.dropdown-menu' do - click_link 'master' + expect do + click_on 'Run pipeline' + wait_for_requests end - - expect { click_on 'Run pipeline' } .to change { Ci::Pipeline.count }.by(1) end end @@ -760,14 +749,13 @@ RSpec.describe 'Pipelines', :js do let(:project) { create(:project, :repository) } before do - stub_feature_flags(new_pipeline_form: false) visit new_project_pipeline_path(project) end describe 'new pipeline page' do it 'has field to add a new pipeline' do - expect(page).to have_selector('.js-branch-select') - expect(find('.js-branch-select')).to have_content project.default_branch + expect(page).to have_selector('[data-testid="ref-select"]') + expect(find('[data-testid="ref-select"]')).to have_content project.default_branch expect(page).to have_content('Run for') end end @@ -776,10 +764,10 @@ RSpec.describe 'Pipelines', :js do it 'shows filtered pipelines', :js do click_button project.default_branch - page.within '.dropdown-menu' do - find('.dropdown-input-field').native.send_keys('fix') + page.within '[data-testid="ref-select"]' do + find('[data-testid="search-refs"]').native.send_keys('fix') - page.within '.dropdown-content' do + page.within '.gl-new-dropdown-contents' do expect(page).to have_content('fix') end end diff --git a/spec/features/projects/product_analytics/events_spec.rb b/spec/features/projects/product_analytics/events_spec.rb index 12f1c4d291a..05d12e12acb 100644 --- a/spec/features/projects/product_analytics/events_spec.rb +++ b/spec/features/projects/product_analytics/events_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe 'Product Analytics > Events' do let_it_be(:project) { create(:project_empty_repo) } let_it_be(:user) { create(:user) } + let(:event) { create(:product_analytics_event, project: project) } 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 aabbc8cea7b..d8a55fc7f3b 100644 --- a/spec/features/projects/releases/user_views_releases_spec.rb +++ b/spec/features/projects/releases/user_views_releases_spec.rb @@ -19,143 +19,129 @@ RSpec.describe 'User views releases', :js do project.add_guest(guest) end - shared_examples 'releases page' do - context('when the user is a maintainer') do - before do - sign_in(maintainer) + context('when the user is a maintainer') do + before do + sign_in(maintainer) + + visit project_releases_path(project) + end - visit project_releases_path(project) + 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 - 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') + context 'when there is a link as an asset' do + 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_v1) << "/downloads#{release_link.filepath}" } + + it 'sees the link' 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 as an asset' do - let!(:release_link) { create(:release_link, release: release_v1, url: url ) } + context 'when there is a link redirect' do + 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" } - let(:direct_asset_link) { Gitlab::Routing.url_helpers.project_release_url(project, release_v1) << "/downloads#{release_link.filepath}" } - it 'sees the link' do + it 'sees the link', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/329301' 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 + end - context 'when there is a link redirect' do - 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 - 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 - end - - context 'when url points to external resource' do - let(:url) { 'http://google.com/download' } + context 'when url points to external resource' do + let(:url) { 'http://google.com/download' } - it 'sees that the link is external resource' do - page.within("##{release_v1.tag} .js-assets-list") do - expect(page).to have_css('[data-testid="external-link-indicator"]') - end + it 'sees that the link is external resource', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/329302' do + page.within("##{release_v1.tag} .js-assets-list") do + expect(page).to have_css('[data-testid="external-link-indicator"]') end end end + end - context 'with an upcoming release' do - it 'sees the upcoming tag' do - page.within("##{release_v3.tag}") do - expect(page).to have_content('Upcoming Release') - end + context 'with an upcoming release' do + it 'sees the upcoming tag' do + 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 - page.within("##{release_v2.tag.parameterize}") do - expect(page).to have_content(release_v2.name) - expect(page).to have_content(release_v2.tag) - end + context 'with a tag containing a slash' do + it 'sees the release' do + 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 'sorting' do + def sort_page(by:, direction:) + within '[data-testid="releases-sort"]' do + find('.dropdown-toggle').click - context "when the page is sorted by the default sort order" do - let(:expected_releases) { [release_v3, release_v2, release_v1] } + click_button(by, class: 'dropdown-item') - it_behaves_like 'releases sort order' + find('.sorting-direction-button').click if direction == :ascending end + end - context "when the page is sorted by created_at ascending " do - let(:expected_releases) { [release_v2, release_v1, release_v3] } + shared_examples 'releases sort order' do + it "sorts the releases #{description}" do + card_titles = page.all('.release-block .card-title', minimum: expected_releases.count) - before do - sort_page by: 'Created date', direction: :ascending + card_titles.each_with_index do |title, index| + expect(title).to have_content(expected_releases[index].name) end - - it_behaves_like 'releases sort order' end end - end - context('when the user is a guest') do - before do - sign_in(guest) - end + context "when the page is sorted by the default sort order" do + let(:expected_releases) { [release_v3, release_v2, release_v1] } - it 'renders release info except for Git-related data' do - visit project_releases_path(project) + it_behaves_like 'releases sort order' + end - within('.release-block', match: :first) do - expect(page).to have_content(release_v3.description) + context "when the page is sorted by created_at ascending " do + let(:expected_releases) { [release_v2, release_v1, release_v3] } - # The following properties (sometimes) include Git info, - # so they are not rendered for Guest users - 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) + before do + sort_page by: 'Created date', direction: :ascending end + + it_behaves_like 'releases sort order' end end end - context 'when the graphql_releases_page feature flag is enabled' do - it_behaves_like 'releases page' - end - - context 'when the graphql_releases_page feature flag is disabled' do + context('when the user is a guest') do before do - stub_feature_flags(graphql_releases_page: false) + sign_in(guest) end - it_behaves_like 'releases page' + it 'renders release info except for Git-related data' do + visit project_releases_path(project) + + 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_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 end diff --git a/spec/features/projects/services/user_activates_asana_spec.rb b/spec/features/projects/services/user_activates_asana_spec.rb deleted file mode 100644 index cf2290383e8..00000000000 --- a/spec/features/projects/services/user_activates_asana_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'User activates Asana' do - include_context 'project service activation' - - it 'activates service', :js do - visit_project_integration('Asana') - fill_in('API key', with: 'verySecret') - fill_in('Restrict to branch', with: 'verySecret') - - click_test_then_save_integration - - expect(page).to have_content('Asana settings saved and active.') - end -end diff --git a/spec/features/projects/services/user_activates_assembla_spec.rb b/spec/features/projects/services/user_activates_assembla_spec.rb deleted file mode 100644 index 63cc424a641..00000000000 --- a/spec/features/projects/services/user_activates_assembla_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'User activates Assembla' do - include_context 'project service activation' - - before do - stub_request(:post, /.*atlas.assembla.com.*/) - end - - it 'activates service', :js do - visit_project_integration('Assembla') - fill_in('Token', with: 'verySecret') - - click_test_then_save_integration(expect_test_to_fail: false) - - expect(page).to have_content('Assembla settings saved and active.') - end -end diff --git a/spec/features/projects/services/user_activates_atlassian_bamboo_ci_spec.rb b/spec/features/projects/services/user_activates_atlassian_bamboo_ci_spec.rb deleted file mode 100644 index 91db375be3a..00000000000 --- a/spec/features/projects/services/user_activates_atlassian_bamboo_ci_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'User activates Atlassian Bamboo CI' do - include_context 'project service activation' - - before do - stub_request(:get, /.*bamboo.example.com.*/) - end - - it 'activates service', :js do - visit_project_integration('Atlassian Bamboo') - fill_in('Bamboo URL', with: 'http://bamboo.example.com') - fill_in('Build key', with: 'KEY') - fill_in('Username', with: 'user') - fill_in('Password', with: 'verySecret') - - click_test_then_save_integration(expect_test_to_fail: false) - - expect(page).to have_content('Atlassian Bamboo settings saved and active.') - - # Password field should not be filled in. - click_link('Atlassian Bamboo') - - expect(find_field('Enter new password').value).to be_blank - expect(page).to have_content('Leave blank to use your current password') - end -end diff --git a/spec/features/projects/services/user_activates_issue_tracker_spec.rb b/spec/features/projects/services/user_activates_issue_tracker_spec.rb index 1aec8883395..019d50a497b 100644 --- a/spec/features/projects/services/user_activates_issue_tracker_spec.rb +++ b/spec/features/projects/services/user_activates_issue_tracker_spec.rb @@ -87,6 +87,6 @@ RSpec.describe 'User activates issue tracker', :js do it_behaves_like 'external issue tracker activation', tracker: 'Redmine' it_behaves_like 'external issue tracker activation', tracker: 'YouTrack', skip_new_issue_url: true it_behaves_like 'external issue tracker activation', tracker: 'Bugzilla' - it_behaves_like 'external issue tracker activation', tracker: 'Custom Issue Tracker' + it_behaves_like 'external issue tracker activation', tracker: 'Custom issue tracker' it_behaves_like 'external issue tracker activation', tracker: 'EWM', skip_test: true end diff --git a/spec/features/projects/settings/access_tokens_spec.rb b/spec/features/projects/settings/access_tokens_spec.rb index 8083c851bb7..76d5d7308d1 100644 --- a/spec/features/projects/settings/access_tokens_spec.rb +++ b/spec/features/projects/settings/access_tokens_spec.rb @@ -99,7 +99,7 @@ RSpec.describe 'Project > Settings > Access Tokens', :js do visit project_settings_access_tokens_path(personal_project) expect(page).to have_selector('#new_project_access_token') - expect(page).to have_text('You can generate an access token scoped to this project for each application to use the GitLab API.') + expect(page).to have_text('Generate project access tokens scoped to this project for your applications that need access to the GitLab API.') end end diff --git a/spec/features/projects/settings/monitor_settings_spec.rb b/spec/features/projects/settings/monitor_settings_spec.rb new file mode 100644 index 00000000000..64138e0aeca --- /dev/null +++ b/spec/features/projects/settings/monitor_settings_spec.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Projects > Settings > For a forked project', :js do + let_it_be(:project) { create(:project, :repository, create_templates: :issue) } + + let(:user) { project.owner} + + before do + sign_in(user) + end + + describe 'Sidebar > Monitor' do + it 'renders the menu in the sidebar' do + visit project_path(project) + wait_for_requests + + expect(page).to have_selector('.sidebar-sub-level-items a[aria-label="Monitor"]', text: 'Monitor', visible: false) + end + + context 'when feature flag sidebar_refactor is disabled' do + it 'renders the menu "Operations" in the sidebar' do + stub_feature_flags(sidebar_refactor: false) + + visit project_path(project) + wait_for_requests + + expect(page).to have_selector('.sidebar-sub-level-items a[aria-label="Operations"]', text: 'Operations', visible: false) + end + end + end + + describe 'Settings > Monitor' do + describe 'Incidents' do + let(:create_issue) { 'Create an incident. Incidents are created for each alert triggered.' } + let(:send_email) { 'Send a single email notification to Owners and Maintainers for new alerts.' } + + before do + create(:project_incident_management_setting, send_email: true, project: project) + visit project_settings_operations_path(project) + + wait_for_requests + click_expand_incident_management_button + end + + it 'renders form for incident management' do + expect(page).to have_selector('h4', text: 'Incidents') + end + + it 'sets correct default values' do + expect(find_field(create_issue)).not_to be_checked + expect(find_field(send_email)).to be_checked + end + + it 'updates form values' do + check(create_issue) + uncheck(send_email) + click_on('No template selected') + click_on('bug') + + save_form + click_expand_incident_management_button + + expect(find_field(create_issue)).to be_checked + expect(page).to have_selector(:id, 'alert-integration-settings-issue-template', text: 'bug') + expect(find_field(send_email)).not_to be_checked + end + + def click_expand_incident_management_button + within '.qa-incident-management-settings' do + click_button('Expand') + end + end + + def save_form + page.within ".qa-incident-management-settings" do + click_on 'Save changes' + end + end + end + + context 'error tracking settings form' do + let(:sentry_list_projects_url) { 'http://sentry.example.com/api/0/projects/' } + + context 'success path' do + let(:projects_sample_response) do + Gitlab::Utils.deep_indifferent_access( + Gitlab::Json.parse(fixture_file('sentry/list_projects_sample_response.json')) + ) + end + + before do + WebMock.stub_request(:get, sentry_list_projects_url) + .to_return( + status: 200, + headers: { 'Content-Type' => 'application/json' }, + body: projects_sample_response.to_json + ) + end + + it 'successfully fills and submits the form' do + visit project_settings_operations_path(project) + + wait_for_requests + + within '.js-error-tracking-settings' do + click_button('Expand') + end + expect(page).to have_content('Sentry API URL') + expect(page.body).to include('Error Tracking') + expect(page).to have_button('Connect') + + check('Active') + fill_in('error-tracking-api-host', with: 'http://sentry.example.com') + fill_in('error-tracking-token', with: 'token') + + click_button('Connect') + + within('div#project-dropdown') do + click_button('Select project') + click_button('Sentry | internal') + end + + click_button('Save changes') + + wait_for_requests + + assert_text('Your changes have been saved') + end + end + + context 'project dropdown fails to load' do + before do + WebMock.stub_request(:get, sentry_list_projects_url) + .to_return( + status: 400, + headers: { 'Content-Type' => 'application/json' }, + body: { + message: 'Sentry response code: 401' + }.to_json + ) + end + + it 'displays error message' do + visit project_settings_operations_path(project) + + wait_for_requests + + within '.js-error-tracking-settings' do + click_button('Expand') + end + check('Active') + fill_in('error-tracking-api-host', with: 'http://sentry.example.com') + fill_in('error-tracking-token', with: 'token') + + click_button('Connect') + + assert_text('Connection failed. Check Auth Token and try again.') + end + end + end + + context 'grafana integration settings form' do + it 'successfully fills and completes the form' do + visit project_settings_operations_path(project) + + wait_for_requests + + within '.js-grafana-integration' do + click_button('Expand') + end + + expect(page).to have_content('Grafana URL') + expect(page).to have_content('API token') + expect(page).to have_button('Save changes') + + fill_in('grafana-url', with: 'http://gitlab-test.grafana.net') + fill_in('grafana-token', with: 'token') + + click_button('Save changes') + + wait_for_requests + + assert_text('Your changes have been saved') + end + end + end +end diff --git a/spec/features/projects/settings/operations_settings_spec.rb b/spec/features/projects/settings/operations_settings_spec.rb deleted file mode 100644 index ca976997142..00000000000 --- a/spec/features/projects/settings/operations_settings_spec.rb +++ /dev/null @@ -1,179 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Projects > Settings > For a forked project', :js do - let(:user) { create(:user) } - let(:project) { create(:project, :repository, create_templates: :issue) } - let(:role) { :maintainer } - - before do - sign_in(user) - project.add_role(user, role) - end - - describe 'Sidebar > Operations' do - it 'renders the settings link in the sidebar' do - visit project_path(project) - wait_for_requests - - expect(page).to have_selector('a[title="Operations"]', visible: false) - end - end - - describe 'Settings > Operations' do - describe 'Incidents' do - let(:create_issue) { 'Create an incident. Incidents are created for each alert triggered.' } - let(:send_email) { 'Send a single email notification to Owners and Maintainers for new alerts.' } - - before do - create(:project_incident_management_setting, send_email: true, project: project) - visit project_settings_operations_path(project) - - wait_for_requests - click_expand_incident_management_button - end - - it 'renders form for incident management' do - expect(page).to have_selector('h4', text: 'Incidents') - end - - it 'sets correct default values' do - expect(find_field(create_issue)).not_to be_checked - expect(find_field(send_email)).to be_checked - end - - it 'updates form values' do - check(create_issue) - uncheck(send_email) - click_on('No template selected') - click_on('bug') - - save_form - click_expand_incident_management_button - - expect(find_field(create_issue)).to be_checked - expect(page).to have_selector(:id, 'alert-integration-settings-issue-template', text: 'bug') - expect(find_field(send_email)).not_to be_checked - end - - def click_expand_incident_management_button - within '.qa-incident-management-settings' do - click_button('Expand') - end - end - - def save_form - page.within ".qa-incident-management-settings" do - click_on 'Save changes' - end - end - end - - context 'error tracking settings form' do - let(:sentry_list_projects_url) { 'http://sentry.example.com/api/0/projects/' } - - context 'success path' do - let(:projects_sample_response) do - Gitlab::Utils.deep_indifferent_access( - Gitlab::Json.parse(fixture_file('sentry/list_projects_sample_response.json')) - ) - end - - before do - WebMock.stub_request(:get, sentry_list_projects_url) - .to_return( - status: 200, - headers: { 'Content-Type' => 'application/json' }, - body: projects_sample_response.to_json - ) - end - - it 'successfully fills and submits the form' do - visit project_settings_operations_path(project) - - wait_for_requests - - within '.js-error-tracking-settings' do - click_button('Expand') - end - expect(page).to have_content('Sentry API URL') - expect(page.body).to include('Error Tracking') - expect(page).to have_button('Connect') - - check('Active') - fill_in('error-tracking-api-host', with: 'http://sentry.example.com') - fill_in('error-tracking-token', with: 'token') - - click_button('Connect') - - within('div#project-dropdown') do - click_button('Select project') - click_button('Sentry | internal') - end - - click_button('Save changes') - - wait_for_requests - - assert_text('Your changes have been saved') - end - end - - context 'project dropdown fails to load' do - before do - WebMock.stub_request(:get, sentry_list_projects_url) - .to_return( - status: 400, - headers: { 'Content-Type' => 'application/json' }, - body: { - message: 'Sentry response code: 401' - }.to_json - ) - end - - it 'displays error message' do - visit project_settings_operations_path(project) - - wait_for_requests - - within '.js-error-tracking-settings' do - click_button('Expand') - end - check('Active') - fill_in('error-tracking-api-host', with: 'http://sentry.example.com') - fill_in('error-tracking-token', with: 'token') - - click_button('Connect') - - assert_text('Connection failed. Check Auth Token and try again.') - end - end - end - - context 'grafana integration settings form' do - it 'successfully fills and completes the form' do - visit project_settings_operations_path(project) - - wait_for_requests - - within '.js-grafana-integration' do - click_button('Expand') - end - - expect(page).to have_content('Grafana URL') - expect(page).to have_content('API token') - expect(page).to have_button('Save changes') - - fill_in('grafana-url', with: 'http://gitlab-test.grafana.net') - fill_in('grafana-token', with: 'token') - - click_button('Save changes') - - wait_for_requests - - assert_text('Your changes have been saved') - end - end - end -end diff --git a/spec/features/projects/settings/packages_settings_spec.rb b/spec/features/projects/settings/packages_settings_spec.rb index 0b40cbee582..62f31fd027b 100644 --- a/spec/features/projects/settings/packages_settings_spec.rb +++ b/spec/features/projects/settings/packages_settings_spec.rb @@ -3,36 +3,32 @@ require 'spec_helper' RSpec.describe 'Projects > Settings > Packages', :js do - let(:project) { create(:project) } - let(:user) { create(:user) } + let_it_be(:project) { create(:project) } + + let(:user) { project.owner } before do sign_in(user) - project.add_maintainer(user) + + stub_config(packages: { enabled: packages_enabled }) + + visit edit_project_path(project) end context 'Packages enabled in config' do - before do - allow(Gitlab.config.packages).to receive(:enabled).and_return(true) - end + let(:packages_enabled) { true } it 'displays the packages toggle button' do - visit edit_project_path(project) - - expect(page).to have_content('Packages') + expect(page).to have_button('Packages', class: 'gl-toggle') expect(page).to have_selector('input[name="project[packages_enabled]"] + button', visible: true) end end context 'Packages disabled in config' do - before do - allow(Gitlab.config.packages).to receive(:enabled).and_return(false) - end + let(:packages_enabled) { false } it 'does not show up in UI' do - visit edit_project_path(project) - - expect(page).not_to have_content('Packages') + expect(page).not_to have_button('Packages', class: 'gl-toggle') end end end diff --git a/spec/features/projects/settings/project_settings_spec.rb b/spec/features/projects/settings/project_settings_spec.rb index cd1c9ecde9c..71b319d192c 100644 --- a/spec/features/projects/settings/project_settings_spec.rb +++ b/spec/features/projects/settings/project_settings_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe 'Projects settings' do let_it_be(:project) { create(:project) } + let(:user) { project.owner } let(:panel) { find('.general-settings', match: :first) } let(:button) { panel.find('.btn.gl-button.js-settings-toggle') } diff --git a/spec/features/projects/settings/registry_settings_spec.rb b/spec/features/projects/settings/registry_settings_spec.rb index bc60cdd2f8e..6a2769d11fd 100644 --- a/spec/features/projects/settings/registry_settings_spec.rb +++ b/spec/features/projects/settings/registry_settings_spec.rb @@ -11,105 +11,125 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p let(:container_registry_enabled) { true } let(:container_registry_enabled_on_project) { true } - subject { visit project_settings_ci_cd_path(project) } + shared_examples 'an expiration policy form' do + before do + project.update!(container_registry_enabled: container_registry_enabled_on_project) + project.container_expiration_policy.update!(enabled: true) - 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) + end - sign_in(user) - stub_container_registry_config(enabled: container_registry_enabled) - end + context 'as owner' do + it 'shows available section' do + subject - context 'as owner' do - it 'shows available section' do - subject + settings_block = find('#js-registry-policies') + expect(settings_block).to have_text 'Clean up image tags' + end - settings_block = find('#js-registry-policies') - expect(settings_block).to have_text 'Clean up image tags' - end + it 'saves cleanup policy submit the form' do + subject - it 'saves cleanup policy submit the form' do - subject + within '#js-registry-policies' do + select('Every day', from: 'Run cleanup') + select('50 tags per image name', from: 'Keep the most recent:') + fill_in('Keep tags matching:', with: 'stable') + select('7 days', from: 'Remove tags older than:') + fill_in('Remove tags matching:', with: '.*-production') + + submit_button = find('[data-testid="save-button"') + expect(submit_button).not_to be_disabled + submit_button.click + end - within '#js-registry-policies' do - select('Every day', from: 'Run cleanup') - select('50 tags per image name', from: 'Keep the most recent:') - fill_in('Keep tags matching:', with: 'stable') - select('7 days', from: 'Remove tags older than:') - fill_in('Remove tags matching:', with: '.*-production') + expect(find('.gl-toast')).to have_content('Cleanup policy successfully saved.') + end - submit_button = find('[data-testid="save-button"') - expect(submit_button).not_to be_disabled - submit_button.click + it 'does not save cleanup policy submit form with invalid regex' do + subject + + within '#js-registry-policies' do + fill_in('Remove tags matching:', with: '*-production') + + submit_button = find('[data-testid="save-button"') + expect(submit_button).not_to be_disabled + submit_button.click + end + + expect(find('.gl-toast')).to have_content('Something went wrong while updating the cleanup policy.') end - toast = find('.gl-toast') - expect(toast).to have_content('Cleanup policy successfully saved.') end - it 'does not save cleanup policy submit form with invalid regex' do - subject + context 'with a project without expiration policy' do + where(:application_setting, :feature_flag, :result) do + true | true | :available_section + true | false | :available_section + false | true | :available_section + false | false | :disabled_message + end - within '#js-registry-policies' do - fill_in('Remove tags matching:', with: '*-production') + with_them do + before do + project.container_expiration_policy.destroy! + stub_feature_flags(container_expiration_policies_historic_entry: false) + stub_application_setting(container_expiration_policies_enable_historic_entries: application_setting) + stub_feature_flags(container_expiration_policies_historic_entry: project) if feature_flag + end - submit_button = find('[data-testid="save-button"') - expect(submit_button).not_to be_disabled - submit_button.click + it 'displays the expected result' do + subject + + within '#js-registry-policies' do + case result + when :available_section + expect(find('[data-testid="enable-toggle"]')).to have_content('Disabled - Tags will not be automatically deleted.') + when :disabled_message + expect(find('.gl-alert-title')).to have_content('Cleanup policy for tags is disabled') + end + end + end end - toast = find('.gl-toast') - expect(toast).to have_content('Something went wrong while updating the cleanup policy.') end - end - context 'with a project without expiration policy' do - where(:application_setting, :feature_flag, :result) do - true | true | :available_section - true | false | :available_section - false | true | :available_section - false | false | :disabled_message - end + context 'when registry is disabled' do + let(:container_registry_enabled) { false } + + it 'does not exists' do + subject - with_them do - before do - project.container_expiration_policy.destroy! - stub_feature_flags(container_expiration_policies_historic_entry: false) - stub_application_setting(container_expiration_policies_enable_historic_entries: application_setting) - stub_feature_flags(container_expiration_policies_historic_entry: project) if feature_flag + expect(page).not_to have_selector('#js-registry-policies') end + end - it 'displays the expected result' do + context 'when container registry is disabled on project' do + let(:container_registry_enabled_on_project) { false } + + it 'does not exists' do subject - within '#js-registry-policies' do - case result - when :available_section - expect(find('[data-testid="enable-toggle"]')).to have_content('Disabled - Tags will not be automatically deleted.') - when :disabled_message - expect(find('.gl-alert-title')).to have_content('Cleanup policy for tags is disabled') - end - end + expect(page).not_to have_selector('#js-registry-policies') end end end - context 'when registry is disabled' do - let(:container_registry_enabled) { false } + context 'with sidebar feature flag off' do + subject { visit project_settings_ci_cd_path(project) } - it 'does not exists' do - subject - - expect(page).not_to have_selector('#js-registry-policies') + before do + stub_feature_flags(sidebar_refactor: false) end - end - context 'when container registry is disabled on project' do - let(:container_registry_enabled_on_project) { false } + it_behaves_like 'an expiration policy form' + end - it 'does not exists' do - subject + context 'with sidebar feature flag on' do + subject { visit project_settings_packages_and_registries_path(project) } - expect(page).not_to have_selector('#js-registry-policies') + before do + stub_feature_flags(sidebar_refactor: true) end + + it_behaves_like 'an expiration policy form' end end diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb index 2f257d299d8..f420a8a76b9 100644 --- a/spec/features/projects/settings/repository_settings_spec.rb +++ b/spec/features/projects/settings/repository_settings_spec.rb @@ -42,6 +42,7 @@ RSpec.describe 'Projects > Settings > Repository settings' do context 'Deploy Keys', :js do let_it_be(:private_deploy_key) { create(:deploy_key, title: 'private_deploy_key', public: false) } let_it_be(:public_deploy_key) { create(:another_deploy_key, title: 'public_deploy_key', public: true) } + let(:new_ssh_key) { attributes_for(:key)[:key] } it 'get list of keys' do @@ -116,7 +117,8 @@ RSpec.describe 'Projects > Settings > Repository settings' do project.deploy_keys << private_deploy_key visit project_settings_repository_path(project) - accept_confirm { find('.deploy-key', text: private_deploy_key.title).find('[data-testid="remove-icon"]').click } + click_button 'Remove' + click_button 'Remove deploy key' expect(page).not_to have_content(private_deploy_key.title) end diff --git a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb index ebda5c9ff59..bf90e86c263 100644 --- a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb +++ b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb @@ -163,7 +163,8 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do click_on('Save changes') end - find('.flash-notice') + wait_for_requests + radio = find_field('project_project_setting_attributes_squash_option_default_on') expect(radio).to be_checked @@ -178,7 +179,8 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do click_on('Save changes') end - find('.flash-notice') + wait_for_requests + radio = find_field('project_project_setting_attributes_squash_option_always') expect(radio).to be_checked @@ -193,7 +195,8 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do click_on('Save changes') end - find('.flash-notice') + wait_for_requests + radio = find_field('project_project_setting_attributes_squash_option_never') expect(radio).to be_checked @@ -220,7 +223,8 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do click_on('Save changes') end - find('.flash-notice') + wait_for_requests + radio = find_field('project_project_setting_attributes_mr_default_target_self_true') expect(radio).to be_checked diff --git a/spec/features/projects/settings/user_manages_project_members_spec.rb b/spec/features/projects/settings/user_manages_project_members_spec.rb index b237e7e8ce7..be4b6d6b82d 100644 --- a/spec/features/projects/settings/user_manages_project_members_spec.rb +++ b/spec/features/projects/settings/user_manages_project_members_spec.rb @@ -38,16 +38,12 @@ RSpec.describe 'Projects > Settings > User manages project members' do end it 'imports a team from another project', :js do - stub_feature_flags(invite_members_group_modal: false) - project2.add_maintainer(user) project2.add_reporter(user_mike) visit(project_project_members_path(project)) - page.within('.invite-users-form') do - click_link('Import') - end + click_link('Import a project') select2(project2.id, from: '#source_project_id') click_button('Import project members') @@ -55,6 +51,28 @@ RSpec.describe 'Projects > Settings > User manages project members' do expect(find_member_row(user_mike)).to have_content('Reporter') end + describe 'when the :invite_members_group_modal is disabled' do + before do + stub_feature_flags(invite_members_group_modal: false) + end + + it 'imports a team from another project', :js do + project2.add_maintainer(user) + project2.add_reporter(user_mike) + + visit(project_project_members_path(project)) + + page.within('.invite-users-form') do + click_link('Import') + end + + select2(project2.id, from: '#source_project_id') + click_button('Import project members') + + expect(find_member_row(user_mike)).to have_content('Reporter') + end + end + it 'shows all members of project shared group', :js do group.add_owner(user) group.add_developer(user_dmitriy) diff --git a/spec/features/projects/snippets/user_views_snippets_spec.rb b/spec/features/projects/snippets/user_views_snippets_spec.rb index bc8cba1dc31..40539b43ed5 100644 --- a/spec/features/projects/snippets/user_views_snippets_spec.rb +++ b/spec/features/projects/snippets/user_views_snippets_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe 'Projects > Snippets > User views snippets' do let_it_be(:project) { create(:project) } + let(:user) { create(:user) } def visit_project_snippets diff --git a/spec/features/projects/user_changes_project_visibility_spec.rb b/spec/features/projects/user_changes_project_visibility_spec.rb index 6935ad4be02..39b8cddd005 100644 --- a/spec/features/projects/user_changes_project_visibility_spec.rb +++ b/spec/features/projects/user_changes_project_visibility_spec.rb @@ -28,7 +28,9 @@ RSpec.describe 'User changes public project visibility', :js do click_button 'Reduce project visibility' end - expect(page).to have_text("Project '#{project.name}' was successfully updated") + wait_for_requests + + expect(project.reload).to be_private end end diff --git a/spec/features/projects/user_sees_sidebar_spec.rb b/spec/features/projects/user_sees_sidebar_spec.rb index ff6217d02a7..e2498928fa0 100644 --- a/spec/features/projects/user_sees_sidebar_spec.rb +++ b/spec/features/projects/user_sees_sidebar_spec.rb @@ -198,7 +198,7 @@ RSpec.describe 'Projects > User sees sidebar' do expect(page).to have_content 'Project' expect(page).to have_content 'Issues' expect(page).to have_content 'Wiki' - expect(page).to have_content 'Operations' + expect(page).to have_content 'Monitor' expect(page).not_to have_content 'Repository' expect(page).not_to have_content 'CI/CD' diff --git a/spec/features/projects/user_sees_user_popover_spec.rb b/spec/features/projects/user_sees_user_popover_spec.rb index e357824a533..db451578ff8 100644 --- a/spec/features/projects/user_sees_user_popover_spec.rb +++ b/spec/features/projects/user_sees_user_popover_spec.rb @@ -6,6 +6,7 @@ RSpec.describe 'User sees user popover', :js do include Spec::Support::Helpers::Features::NotesHelpers let_it_be(:project) { create(:project, :repository) } + let(:user) { project.creator } let(:merge_request) do create(:merge_request, source_project: project, target_project: project) diff --git a/spec/features/projects/user_uses_shortcuts_spec.rb b/spec/features/projects/user_uses_shortcuts_spec.rb index b6fde19e0d4..1350ecf6e75 100644 --- a/spec/features/projects/user_uses_shortcuts_spec.rb +++ b/spec/features/projects/user_uses_shortcuts_spec.rb @@ -68,14 +68,27 @@ RSpec.describe 'User uses shortcuts', :js do end context 'when navigating to the Project pages' do - it 'redirects to the details page' do + it 'redirects to the project page' do visit project_issues_path(project) find('body').native.send_key('g') find('body').native.send_key('p') expect(page).to have_active_navigation('Project') - expect(page).to have_active_sub_navigation('Details') + end + + context 'when feature flag :sidebar_refactor is disabled' do + it 'redirects to the details page' do + stub_feature_flags(sidebar_refactor: false) + + visit project_issues_path(project) + + find('body').native.send_key('g') + find('body').native.send_key('p') + + expect(page).to have_active_navigation('Project') + expect(page).to have_active_sub_navigation('Details') + end end it 'redirects to the activity page' do @@ -165,28 +178,62 @@ RSpec.describe 'User uses shortcuts', :js do end end - context 'when navigating to the Operations pages' do + context 'when navigating to the Deployments page' do + it 'redirects to the Environments page' do + find('body').native.send_key('g') + find('body').native.send_key('e') + + expect(page).to have_active_navigation('Deployments') + expect(page).to have_active_sub_navigation('Environments') + end + end + + context 'when navigating to the Monitor pages' do it 'redirects to the Metrics page' do find('body').native.send_key('g') find('body').native.send_key('l') - expect(page).to have_active_navigation('Operations') + expect(page).to have_active_navigation('Monitor') expect(page).to have_active_sub_navigation('Metrics') end - it 'redirects to the Environments page' do - find('body').native.send_key('g') - find('body').native.send_key('e') + context 'when feature flag :sidebar_refactor is disabled' do + before do + stub_feature_flags(sidebar_refactor: false) + end - expect(page).to have_active_navigation('Operations') - expect(page).to have_active_sub_navigation('Environments') + it 'redirects to the Operations page' do + find('body').native.send_key('g') + find('body').native.send_key('l') + + expect(page).to have_active_navigation('Operations') + expect(page).to have_active_sub_navigation('Metrics') + end + + it 'redirects to the Kubernetes page with active Operations' do + find('body').native.send_key('g') + find('body').native.send_key('k') + + expect(page).to have_active_navigation('Operations') + expect(page).to have_active_sub_navigation('Kubernetes') + end + + it 'redirects to the Environments page' do + find('body').native.send_key('g') + find('body').native.send_key('e') + + expect(page).to have_active_navigation('Operations') + expect(page).to have_active_sub_navigation('Environments') + end end + end + context 'when navigating to the Infrastructure pages' do it 'redirects to the Kubernetes page' do find('body').native.send_key('g') find('body').native.send_key('k') - expect(page).to have_active_navigation('Operations') + expect(page).to have_active_navigation('Infrastructure') expect(page).to have_active_sub_navigation('Kubernetes') end end diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index acfb7c2602a..b61a769185e 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -160,50 +160,92 @@ RSpec.describe 'Runners' do end end - context 'when application settings have shared_runners_text' do - let(:shared_runners_text) { 'custom **shared** runners description' } - let(:shared_runners_html) { 'custom shared runners description' } + context 'shared runner text' do + context 'when application settings have no shared_runners_text' do + it 'user sees default shared runners description' do + visit project_runners_path(project) - before do - stub_application_setting(shared_runners_text: shared_runners_text) + page.within("[data-testid='shared-runners-description']") do + expect(page).to have_content('The same shared runner executes code from multiple projects') + end + end end - it 'user sees shared runners description' do - visit project_runners_path(project) + context 'when application settings have shared_runners_text' do + let(:shared_runners_text) { 'custom **shared** runners description' } + let(:shared_runners_html) { 'custom shared runners description' } + + before do + stub_application_setting(shared_runners_text: shared_runners_text) + end + + it 'user sees shared runners description' do + visit project_runners_path(project) - expect(page.find('.shared-runners-description')).to have_content(shared_runners_html) + page.within("[data-testid='shared-runners-description']") do + expect(page).not_to have_content('The same shared runner executes code from multiple projects') + expect(page).to have_content(shared_runners_html) + end + end end - end - end - context 'when a project has disabled shared_runners' do - let(:project) { create(:project, shared_runners_enabled: false) } + context 'when application settings have an unsafe link in shared_runners_text' do + let(:shared_runners_text) { 'link' } - context 'when feature flag: vueify_shared_runners_toggle is disabled' do - before do - stub_feature_flags(vueify_shared_runners_toggle: false) - project.add_maintainer(user) + before do + stub_application_setting(shared_runners_text: shared_runners_text) + end + + it 'user sees no link' do + visit project_runners_path(project) + + page.within("[data-testid='shared-runners-description']") do + expect(page).to have_content('link') + expect(page).not_to have_link('link') + end + end end - it 'user enables shared runners' do - visit project_runners_path(project) + context 'when application settings have an unsafe image in shared_runners_text' do + let(:shared_runners_text) { '' } + + before do + stub_application_setting(shared_runners_text: shared_runners_text) + end - click_on 'Enable shared runners' + it 'user sees image safely' do + visit project_runners_path(project) - expect(page.find('.shared-runners-description')).to have_content('Disable shared runners') - expect(page).not_to have_selector('#toggle-shared-runners-form') + page.within("[data-testid='shared-runners-description']") do + expect(page).to have_css('img') + expect(page).not_to have_css('img[onerror]') + end + end end end + end + + context 'enable shared runners in project settings', :js do + before do + project.add_maintainer(user) + + visit project_runners_path(project) + end - context 'when feature flag: vueify_shared_runners_toggle is enabled' do - before do - project.add_maintainer(user) + context 'when a project has enabled shared_runners' do + let(:project) { create(:project, shared_runners_enabled: true) } + + it 'shared runners toggle is on' do + expect(page).to have_selector('[data-testid="toggle-shared-runners"]') + expect(page).to have_selector('[data-testid="toggle-shared-runners"] .is-checked') end + end - it 'user enables shared runners' do - visit project_runners_path(project) + context 'when a project has disabled shared_runners' do + let(:project) { create(:project, shared_runners_enabled: false) } - expect(page).to have_selector('#toggle-shared-runners-form') + it 'shared runners toggle is off' do + expect(page).not_to have_selector('[data-testid="toggle-shared-runners"] .is-checked') end end end diff --git a/spec/features/snippets/spam_snippets_spec.rb b/spec/features/snippets/spam_snippets_spec.rb index 54a56ac962c..3748a916780 100644 --- a/spec/features/snippets/spam_snippets_spec.rb +++ b/spec/features/snippets/spam_snippets_spec.rb @@ -18,6 +18,7 @@ RSpec.describe 'snippet editor with spam', skip: "Will be handled in https://git Gitlab::CurrentSettings.update!( akismet_enabled: true, akismet_api_key: 'testkey', + spam_check_api_key: 'testkey', recaptcha_enabled: true, recaptcha_site_key: 'test site key', recaptcha_private_key: 'test private key' diff --git a/spec/features/unsubscribe_links_spec.rb b/spec/features/unsubscribe_links_spec.rb index 966d90ab16b..b2d0f29808c 100644 --- a/spec/features/unsubscribe_links_spec.rb +++ b/spec/features/unsubscribe_links_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'Unsubscribe links', :sidekiq_might_not_need_inline do let(:author) { create(:user) } let(:project) { create(:project, :public) } let(:params) { { title: 'A bug!', description: 'Fix it!', assignees: [recipient] } } - let(:issue) { Issues::CreateService.new(project, author, params).execute } + let(:issue) { Issues::CreateService.new(project: project, current_user: author, params: params).execute } let(:mail) { ActionMailer::Base.deliveries.last } let(:body) { Capybara::Node::Simple.new(mail.default_part_body.to_s) } diff --git a/spec/features/user_can_display_performance_bar_spec.rb b/spec/features/user_can_display_performance_bar_spec.rb index b8f41925156..14b5964686f 100644 --- a/spec/features/user_can_display_performance_bar_spec.rb +++ b/spec/features/user_can_display_performance_bar_spec.rb @@ -47,7 +47,7 @@ RSpec.describe 'User can display performance bar', :js do end end - let(:group) { create(:group) } + let_it_be(:group) { create(:group) } before do allow(GitlabPerformanceBarStatsWorker).to receive(:perform_in) @@ -123,4 +123,38 @@ RSpec.describe 'User can display performance bar', :js do end end end + + context 'flamegraphs' do + let_it_be(:user) { create(:user) } + + before_all do + group.add_guest(user) + end + + context 'when user has access' do + before do + stub_application_setting(performance_bar_allowed_group_id: group.id) + + Warden.on_next_request do |proxy| + proxy.set_user(user) + end + end + + it 'renders flamegraph when requested' do + visit root_path(performance_bar: 'flamegraph') + + page.within_frame 'speedscope-iframe' do + expect(page).to have_content('Flamegraph for /') + end + end + end + + context 'when user does not have access' do + it 'renders the original page' do + visit root_path(performance_bar: 'flamegraph') + + expect(page).not_to have_selector('iframe#speedscope-iframe') + end + end + end end diff --git a/spec/features/users/add_email_to_existing_account_spec.rb b/spec/features/users/add_email_to_existing_account_spec.rb index 9130b96b0e3..cf78fc4587f 100644 --- a/spec/features/users/add_email_to_existing_account_spec.rb +++ b/spec/features/users/add_email_to_existing_account_spec.rb @@ -4,13 +4,25 @@ require 'spec_helper' RSpec.describe 'AdditionalEmailToExistingAccount' do describe 'add secondary email associated with account' do - let(:user) { create(:user) } + let_it_be(:user) { create(:user) } + let_it_be(:email) { create(:email, user: user) } - it 'verifies confirmation of additional email' do + before do sign_in(user) + end + + it 'verifies confirmation of additional email' do + visit email_confirmation_path(confirmation_token: email.confirmation_token) + + expect(page).to have_content 'Your email address has been successfully confirmed.' + end + + it 'accepts any pending invites for an email confirmation' do + member = create(:group_member, :invited, invite_email: email.email) - email = create(:email, user: user) visit email_confirmation_path(confirmation_token: email.confirmation_token) + + expect(member.reload.user).to eq(user) expect(page).to have_content 'Your email address has been successfully confirmed.' end end diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb index 5f70517224e..17a6abb99e0 100644 --- a/spec/features/users/signup_spec.rb +++ b/spec/features/users/signup_spec.rb @@ -57,6 +57,12 @@ RSpec.describe 'Signup' do fill_in 'new_user_password', with: new_user.password end + def confirm_email + new_user_token = User.find_by_email(new_user.email).confirmation_token + + visit user_confirmation_path(confirmation_token: new_user_token) + end + describe 'username validation', :js do before do visit new_user_registration_path @@ -191,7 +197,7 @@ RSpec.describe 'Signup' do stub_feature_flags(soft_email_confirmation: false) end - it 'creates the user account and sends a confirmation email' do + it 'creates the user account and sends a confirmation email, and pre-fills email address after confirming' do visit new_user_registration_path fill_in_signup_form @@ -199,6 +205,10 @@ RSpec.describe 'Signup' do expect { click_button 'Register' }.to change { User.count }.by(1) expect(current_path).to eq users_almost_there_path expect(page).to have_content('Please check your email to confirm your account') + + confirm_email + + expect(find_field('Username or email').value).to eq(new_user.email) end end diff --git a/spec/features/users/user_browses_projects_on_user_page_spec.rb b/spec/features/users/user_browses_projects_on_user_page_spec.rb index 7d05b2ae27a..ded90be3924 100644 --- a/spec/features/users/user_browses_projects_on_user_page_spec.rb +++ b/spec/features/users/user_browses_projects_on_user_page_spec.rb @@ -125,7 +125,7 @@ RSpec.describe 'Users > User browses projects on user page', :js do end before do - Issues::CreateService.new(contributed_project, user, { title: 'Bug in old browser' }).execute + Issues::CreateService.new(project: contributed_project, current_user: user, params: { title: 'Bug in old browser' }).execute event = create(:push_event, project: contributed_project, author: user) create(:push_event_payload, event: event, commit_count: 3) end diff --git a/spec/features/whats_new_spec.rb b/spec/features/whats_new_spec.rb index 55b96361f03..2938ea1b1e8 100644 --- a/spec/features/whats_new_spec.rb +++ b/spec/features/whats_new_spec.rb @@ -34,6 +34,24 @@ RSpec.describe "renders a `whats new` dropdown item" do sign_in(user) end + it 'renders dropdown item when feature enabled' do + Gitlab::CurrentSettings.update!(whats_new_variant: ApplicationSetting.whats_new_variants[:all_tiers]) + + visit root_dashboard_path + find('.header-help-dropdown-toggle').click + + expect(page).to have_button(text: "What's new") + end + + it 'does not render dropdown item when feature disabled' do + Gitlab::CurrentSettings.update!(whats_new_variant: ApplicationSetting.whats_new_variants[:disabled]) + + visit root_dashboard_path + find('.header-help-dropdown-toggle').click + + expect(page).not_to have_button(text: "What's new") + end + it 'shows notification dot and count and removes it once viewed' do visit root_dashboard_path -- cgit v1.2.3