diff options
Diffstat (limited to 'spec/features')
168 files changed, 4206 insertions, 3112 deletions
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb index f5c5a73c042..653a45a4bb8 100644 --- a/spec/features/admin/admin_groups_spec.rb +++ b/spec/features/admin/admin_groups_spec.rb @@ -194,7 +194,7 @@ RSpec.describe 'Admin Groups' do expect(page).to have_content('Developer') end - accept_confirm { find(:css, 'li', text: current_user.name).find(:css, 'a.btn-remove').click } + accept_confirm { find(:css, 'li', text: current_user.name).find(:css, 'a.btn-danger').click } visit group_group_members_path(group) diff --git a/spec/features/admin/admin_mode/login_spec.rb b/spec/features/admin/admin_mode/login_spec.rb index 12046518aac..7cbba9ec674 100644 --- a/spec/features/admin/admin_mode/login_spec.rb +++ b/spec/features/admin/admin_mode/login_spec.rb @@ -48,7 +48,7 @@ RSpec.describe 'Admin Mode Login', :clean_gitlab_redis_shared_state, :do_not_moc it 'allows login with valid code' do # Cannot reuse the TOTP - Timecop.travel(30.seconds.from_now) do + travel_to(30.seconds.from_now) do enter_code(user.current_otp) expect(current_path).to eq admin_root_path @@ -58,7 +58,7 @@ RSpec.describe 'Admin Mode Login', :clean_gitlab_redis_shared_state, :do_not_moc it 'blocks login with invalid code' do # Cannot reuse the TOTP - Timecop.travel(30.seconds.from_now) do + travel_to(30.seconds.from_now) do enter_code('foo') expect(page).to have_content('Invalid two-factor code') @@ -67,7 +67,7 @@ RSpec.describe 'Admin Mode Login', :clean_gitlab_redis_shared_state, :do_not_moc it 'allows login with invalid code, then valid code' do # Cannot reuse the TOTP - Timecop.travel(30.seconds.from_now) do + travel_to(30.seconds.from_now) do enter_code('foo') expect(page).to have_content('Invalid two-factor code') @@ -163,7 +163,7 @@ RSpec.describe 'Admin Mode Login', :clean_gitlab_redis_shared_state, :do_not_moc expect(page).to have_content('Two-Factor Authentication') # Cannot reuse the TOTP - Timecop.travel(30.seconds.from_now) do + travel_to(30.seconds.from_now) do enter_code(user.current_otp) expect(current_path).to eq admin_root_path @@ -215,7 +215,7 @@ RSpec.describe 'Admin Mode Login', :clean_gitlab_redis_shared_state, :do_not_moc expect(page).to have_content('Two-Factor Authentication') # Cannot reuse the TOTP - Timecop.travel(30.seconds.from_now) do + travel_to(30.seconds.from_now) do enter_code(user.current_otp) expect(current_path).to eq admin_root_path diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 38f0b813183..528dfad606e 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -130,6 +130,38 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n expect(user_internal_regex['placeholder']).to eq 'Regex pattern' end + context 'Change Sign-up restrictions' do + context 'Require Admin approval for new signup setting' do + context 'when feature is enabled' do + before do + stub_feature_flags(admin_approval_for_new_user_signups: true) + end + + it 'changes the setting' do + page.within('.as-signup') do + check 'Require admin approval for new sign-ups' + click_button 'Save changes' + end + + expect(current_settings.require_admin_approval_after_user_signup).to be_truthy + expect(page).to have_content "Application settings saved successfully" + end + end + + context 'when feature is disabled' do + before do + stub_feature_flags(admin_approval_for_new_user_signups: false) + end + + it 'does not show the the setting' do + page.within('.as-signup') do + expect(page).not_to have_selector('.application_setting_require_admin_approval_after_user_signup') + end + end + end + end + end + it 'Change Sign-in restrictions' do page.within('.as-signin') do fill_in 'Home page URL', with: 'https://about.gitlab.com/' @@ -497,18 +529,23 @@ RSpec.describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_n end it 'Change Help page' do + stub_feature_flags(help_page_documentation_redirect: true) + new_support_url = 'http://example.com/help' + new_documentation_url = 'https://docs.gitlab.com' page.within('.as-help-page') do fill_in 'Help page text', with: 'Example text' check 'Hide marketing-related entries from help' fill_in 'Support page URL', with: new_support_url + fill_in 'Documentation pages URL', with: new_documentation_url click_button 'Save changes' end expect(current_settings.help_page_text).to eq "Example text" expect(current_settings.help_page_hide_commercial_content).to be_truthy expect(current_settings.help_page_support_url).to eq new_support_url + expect(current_settings.help_page_documentation_base_url).to eq new_documentation_url expect(page).to have_content "Application settings saved successfully" end diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index a37210d2acc..e06e2d14f3c 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -31,6 +31,7 @@ RSpec.describe "Admin::Users" do 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') @@ -48,6 +49,56 @@ RSpec.describe "Admin::Users" do end end + context 'user project count' do + before do + project = create(:project) + project.add_maintainer(current_user) + end + + it 'displays count of users projects' do + visit admin_users_path + + expect(page.find("[data-testid='user-project-count-#{current_user.id}']").text).to eq("1") + end + 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 + + context '`Pending approval` tab' do + context 'feature is enabled' do + before do + stub_feature_flags(admin_approval_for_new_user_signups: true) + visit admin_users_path + end + + it 'shows the `Pending approval` tab' do + expect(page).to have_link('Pending approval', href: admin_users_path(filter: 'blocked_pending_approval')) + end + end + + context 'feature is disabled' do + before do + stub_feature_flags(admin_approval_for_new_user_signups: false) + visit admin_users_path + end + + it 'does not show the `Pending approval` tab' do + expect(page).not_to have_link('Pending approval', href: admin_users_path(filter: 'blocked_pending_approval')) + end + end + end + end + describe 'search and sort' do before do create(:user, name: 'Foo Bar', last_activity_on: 3.days.ago) @@ -146,6 +197,27 @@ RSpec.describe "Admin::Users" do expect(page).to have_content(user.email) end end + + describe 'Pending approval filter' do + it 'counts users who are pending approval' do + create_list(:user, 2, :blocked_pending_approval) + + visit admin_users_path + + page.within('.filter-blocked-pending-approval small') do + expect(page).to have_content('2') + end + end + + it 'filters by users who are pending approval' do + user = create(:user, :blocked_pending_approval) + + visit admin_users_path + click_link 'Pending approval' + + expect(page).to have_content(user.email) + end + end end describe "GET /admin/users/new" do @@ -287,6 +359,23 @@ RSpec.describe "Admin::Users" do expect(page).to have_button('Delete user and contributions') end + context 'user pending approval' do + it 'shows user info' 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_button('Block user') + expect(page).to have_button('Delete user') + expect(page).to have_button('Delete user and contributions') + end + end + describe 'Impersonation' do let(:another_user) { create(:user) } @@ -606,7 +695,7 @@ RSpec.describe "Admin::Users" do end end - describe 'show user keys' do + describe 'show user keys', :js do let!(:key1) do 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") end @@ -629,7 +718,11 @@ RSpec.describe "Admin::Users" do expect(page).to have_content(key2.title) expect(page).to have_content(key2.key) - click_link 'Remove' + click_button 'Delete' + + page.within('.modal') do + page.click_button('Delete') + end expect(page).not_to have_content(key2.title) end diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb index b8851c28531..44642983a36 100644 --- a/spec/features/admin/admin_uses_repository_checks_spec.rb +++ b/spec/features/admin/admin_uses_repository_checks_spec.rb @@ -46,7 +46,7 @@ RSpec.describe 'Admin uses repository checks', :request_store, :clean_gitlab_red ) visit_admin_project_page(project) - page.within('.alert') do + page.within('.gl-alert') do expect(page.text).to match(/Last repository check \(just now\) failed/) end end diff --git a/spec/features/admin/clusters/eks_spec.rb b/spec/features/admin/clusters/eks_spec.rb index ef49aebc7c5..ad7122bf182 100644 --- a/spec/features/admin/clusters/eks_spec.rb +++ b/spec/features/admin/clusters/eks_spec.rb @@ -13,7 +13,7 @@ RSpec.describe 'Instance-level AWS EKS Cluster', :js do before do visit admin_clusters_path - click_link 'Add Kubernetes cluster' + click_link 'Integrate with a cluster certificate' end context 'when user creates a cluster on AWS EKS' do diff --git a/spec/features/admin/dashboard_spec.rb b/spec/features/admin/dashboard_spec.rb index 4ffa5e3be0b..acb8fb54e11 100644 --- a/spec/features/admin/dashboard_spec.rb +++ b/spec/features/admin/dashboard_spec.rb @@ -28,11 +28,9 @@ RSpec.describe 'admin visits dashboard' do describe 'Users statistic' do let_it_be(:users_statistics) { create(:users_statistics) } + let_it_be(:users_count_label) { Gitlab.ee? ? 'Billable users 71' : 'Active users 71' } it 'shows correct amounts of users', :aggregate_failures do - expected_active_users_text = Gitlab.ee? ? 'Active users (Billable users) 71' : 'Active users 71' - - sign_in(create(:admin)) visit admin_dashboard_stats_path expect(page).to have_content('Users without a Group and Project 23') @@ -42,9 +40,9 @@ RSpec.describe 'admin visits dashboard' do expect(page).to have_content('Users with highest role Maintainer 6') expect(page).to have_content('Users with highest role Owner 5') expect(page).to have_content('Bots 2') - expect(page).to have_content(expected_active_users_text) expect(page).to have_content('Blocked users 7') expect(page).to have_content('Total users 78') + expect(page).to have_content(users_count_label) end end end diff --git a/spec/features/alert_management/alert_details_spec.rb b/spec/features/alert_management/alert_details_spec.rb new file mode 100644 index 00000000000..d190e4b6939 --- /dev/null +++ b/spec/features/alert_management/alert_details_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Alert details', :js do + let_it_be(:project) { create(:project) } + let_it_be(:developer) { create(:user) } + let_it_be(:alert) { create(:alert_management_alert, project: project, status: 'triggered', title: 'Alert') } + + before_all do + project.add_developer(developer) + end + + before do + sign_in(developer) + + visit details_project_alert_management_path(project, alert) + wait_for_requests + end + + context 'when a developer displays the alert' do + it 'shows the alert' do + page.within('.alert-management-details') do + expect(find('h2')).to have_content(alert.title) + end + end + + it 'shows the alert tabs' do + page.within('.alert-management-details') do + alert_tabs = find('[data-testid="alertDetailsTabs"]') + + expect(alert_tabs).to have_content('Alert details') + end + end + + it 'shows the right sidebar mounted with correct widgets' do + page.within('.layout-page') do + sidebar = find('.right-sidebar') + + expect(sidebar).to have_selector('.alert-status') + expect(sidebar).to have_selector('.alert-assignees') + expect(sidebar).to have_content('Triggered') + end + end + + it 'updates the alert todo button from the right sidebar' do + expect(page).to have_selector('[data-testid="alert-todo-button"]') + todo_button = find('[data-testid="alert-todo-button"]') + + expect(todo_button).to have_content('Add a To-Do') + find('[data-testid="alert-todo-button"]').click + wait_for_requests + + expect(todo_button).to have_content('Mark as done') + end + + it 'updates the alert status from the right sidebar' do + page.within('.alert-status') do + alert_status = find('[data-testid="status"]') + + expect(alert_status).to have_content('Triggered') + + find('.btn-link').click + find('.gl-new-dropdown-item', text: 'Acknowledged').click + + wait_for_requests + + expect(alert_status).to have_content('Acknowledged') + end + end + + it 'updates the alert assignee from the right sidebar' do + page.within('.right-sidebar') do + alert_assignee = find('.alert-assignees') + + expect(alert_assignee).to have_content('None - assign yourself') + + find('[data-testid="unassigned-users"]').click + + wait_for_requests + + expect(alert_assignee).to have_content('Assignee Edit John Doe') + end + end + end +end diff --git a/spec/features/alert_management/alert_management_list_spec.rb b/spec/features/alert_management/alert_management_list_spec.rb new file mode 100644 index 00000000000..c2514d80474 --- /dev/null +++ b/spec/features/alert_management/alert_management_list_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Alert Management index', :js do + let_it_be(:project) { create(:project) } + let_it_be(:developer) { create(:user) } + let_it_be(:alert) { create(:alert_management_alert, project: project, status: 'triggered') } + + before_all do + project.add_developer(developer) + end + + before do + sign_in(developer) + + visit project_alert_management_index_path(project) + wait_for_requests + end + + context 'when a developer displays the alert list and the alert service is not enabled' do + it 'shows the alert page title' do + expect(page).to have_content('Alerts') + end + + it 'shows the empty state by default' do + expect(page).to have_content('Surface alerts in GitLab') + end + + it 'does not show the filtered search' do + page.within('.layout-page') do + expect(page).not_to have_css('[data-testid="search-icon"]') + end + end + + it 'does not show the alert table' do + expect(page).not_to have_selector('.gl-table') + end + end + + context 'when a developer displays the alert list and the alert service is enabled' do + let_it_be(:alerts_service) { create(:alerts_service, project: project) } + + it 'shows the alert page title' do + expect(page).to have_content('Alerts') + end + + it 'shows the filtered search' do + page.within('.layout-page') do + expect(page).to have_css('[data-testid="search-icon"]') + end + end + + it 'shows the alert table' do + expect(page).to have_selector('.gl-table') + end + end +end diff --git a/spec/features/alert_management/user_filters_alerts_by_status_spec.rb b/spec/features/alert_management/user_filters_alerts_by_status_spec.rb new file mode 100644 index 00000000000..ee516418cd6 --- /dev/null +++ b/spec/features/alert_management/user_filters_alerts_by_status_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User filters Alert Management table by status', :js do + let_it_be(:project) { create(:project) } + let_it_be(:developer) { create(:user) } + let_it_be(:alerts_service) { create(:alerts_service, project: project) } + let_it_be(:alert1, reload: true) { create(:alert_management_alert, :triggered, project: project) } + let_it_be(:alert2, reload: true) { create(:alert_management_alert, :acknowledged, project: project) } + let_it_be(:alert3, reload: true) { create(:alert_management_alert, :acknowledged, project: project) } + + before_all do + project.add_developer(developer) + end + + before do + sign_in(developer) + + visit project_alert_management_index_path(project) + wait_for_requests + end + + context 'when a developer displays the alert list and the alert service is enabled they can filter the table by an alert status' do + it 'shows the alert table items with alert status of Open by default' do + expect(page).to have_selector('.gl-table') + expect(page).to have_content('Open 3') + end + + it 'shows the alert table items with alert status of Acknowledged' do + find('.gl-tab-nav-item', text: 'Acknowledged').click + + expect(page).to have_selector('.gl-tab-nav-item-active') + expect(find('.gl-tab-nav-item-active')).to have_content('Acknowledged 2') + expect(all('.dropdown-menu-selectable').count).to be(2) + end + + it 'shows the alert table items with alert status of Triggered' do + find('.gl-tab-nav-item', text: 'Triggered').click + wait_for_requests + + expect(page).to have_selector('.gl-tab-nav-item-active') + expect(find('.gl-tab-nav-item-active')).to have_content('Triggered 1') + expect(all('.dropdown-menu-selectable').count).to be(1) + end + + it 'shows the an empty table for a status with no alerts' do + find('.gl-tab-nav-item', text: 'Resolved').click + wait_for_requests + + expect(page).to have_selector('.gl-tab-nav-item-active') + expect(find('.gl-tab-nav-item-active')).to have_content('Resolved 0') + expect(page).to have_content('No alerts to display.') + end + end +end diff --git a/spec/features/alert_management/user_searches_alerts_spec.rb b/spec/features/alert_management/user_searches_alerts_spec.rb new file mode 100644 index 00000000000..568321de025 --- /dev/null +++ b/spec/features/alert_management/user_searches_alerts_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User searches Alert Management alerts', :js do + let_it_be(:project) { create(:project) } + let_it_be(:developer) { create(:user) } + let_it_be(:alerts_service) { create(:alerts_service, project: project) } + let_it_be(:alert) { create(:alert_management_alert, project: project, status: 'triggered') } + + before_all do + project.add_developer(developer) + end + + before do + sign_in(developer) + + visit project_alert_management_index_path(project) + wait_for_requests + end + + context 'when a developer displays the alert list and the alert service is enabled they can search an alert' do + it 'shows the incident table with an incident for a valid search filter bar' do + expect(page).to have_selector('.filtered-search-wrapper') + expect(page).to have_selector('.gl-table') + expect(page).to have_css('[data-testid="severityField"]') + expect(all('tbody tr').count).to be(1) + expect(page).not_to have_selector('.empty-state') + end + end +end diff --git a/spec/features/alert_management/user_updates_alert_status_spec.rb b/spec/features/alert_management/user_updates_alert_status_spec.rb new file mode 100644 index 00000000000..8974796662c --- /dev/null +++ b/spec/features/alert_management/user_updates_alert_status_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User updates Alert Management status', :js do + let_it_be(:project) { create(:project) } + let_it_be(:developer) { create(:user) } + let_it_be(:alerts_service) { create(:alerts_service, project: project) } + let_it_be(:alert) { create(:alert_management_alert, project: project, status: 'triggered') } + + before_all do + project.add_developer(developer) + end + + before do + sign_in(developer) + + visit project_alert_management_index_path(project) + wait_for_requests + end + + context 'when a developer+ displays the alerts list and the alert service is enabled they can update an alert status' do + it 'shows the alert table with an alert status dropdown' do + expect(page).to have_selector('.gl-table') + expect(find('.dropdown-menu-selectable')).to have_content('Triggered') + end + + it 'updates the alert status' do + find('.dropdown-menu-selectable').click + find('.dropdown-item', text: 'Acknowledged').click + wait_for_requests + + expect(find('.dropdown-menu-selectable')).to have_content('Acknowledged') + end + end +end diff --git a/spec/features/alert_management_spec.rb b/spec/features/alert_management_spec.rb new file mode 100644 index 00000000000..2989f72e356 --- /dev/null +++ b/spec/features/alert_management_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Alert management', :js do + let_it_be(:project) { create(:project) } + let_it_be(:developer) { create(:user) } + + before_all do + project.add_developer(developer) + end + + context 'when visiting the alert details page' do + let!(:alert) { create(:alert_management_alert, :resolved, :with_fingerprint, title: 'dos-test', project: project, **options) } + let(:options) { {} } + + before do + sign_in(user) + end + + context 'when actor has permission to see the alert' do + let(:user) { developer } + + it 'shows the alert details' do + visit(details_project_alert_management_path(project, alert)) + + within('.alert-management-details-table') do + expect(page).to have_content(alert.title) + end + end + + context 'when alert belongs to an environment' do + let(:options) { { environment: environment } } + let!(:environment) { create(:environment, name: 'production', project: project) } + + it 'shows the environment name' do + visit(details_project_alert_management_path(project, alert)) + + expect(page).to have_link(environment.name, href: project_environment_path(project, environment)) + within('.alert-management-details-table') do + expect(page).to have_content(environment.name) + end + end + + context 'when expose_environment_path_in_alert_details feature flag is disabled' do + before do + stub_feature_flags(expose_environment_path_in_alert_details: false) + end + + it 'does not show the environment name' do + visit(details_project_alert_management_path(project, alert)) + + within('.alert-management-details-table') do + expect(page).to have_content(alert.title) + expect(page).not_to have_content(environment.name) + end + end + end + end + end + end +end diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb index d432825e113..00efca5d3a8 100644 --- a/spec/features/boards/add_issues_modal_spec.rb +++ b/spec/features/boards/add_issues_modal_spec.rb @@ -79,7 +79,7 @@ RSpec.describe 'Issue Boards add issue modal', :js do it 'loads issues' do page.within('.add-issues-modal') do - page.within('.nav-links') do + page.within('.gl-tabs') do expect(page).to have_content('2') end @@ -103,7 +103,13 @@ RSpec.describe 'Issue Boards add issue modal', :js do click_button 'Cancel' end - accept_confirm { first('.board-delete').click } + page.within(find('.board:nth-child(2)')) do + find('button[title="List settings"]').click + end + + page.within(find('.js-board-settings-sidebar')) do + accept_confirm { find('[data-testid="remove-list"]').click } + end click_button('Add issues') @@ -146,7 +152,7 @@ RSpec.describe 'Issue Boards add issue modal', :js do page.within('.add-issues-modal') do first('.board-card .board-card-number').click - page.within('.nav-links') do + page.within('.gl-tabs') do expect(page).to have_content('Selected issues 1') end end diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index e36378bd34e..06ec4e05828 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -24,33 +24,11 @@ RSpec.describe 'Issue Boards', :js do context 'no lists' do before do visit project_board_path(project, board) - wait_for_requests - expect(page).to have_selector('.board', count: 3) - end - - it 'shows blank state' do - expect(page).to have_content('Welcome to your Issue Board!') - end - - it 'shows tooltip on add issues button' do - button = page.find('.filter-dropdown-container button', text: 'Add issues') - - expect(button[:"data-original-title"]).to eq("Please add a list to your board first") - end - - it 'hides the blank state when clicking nevermind button' do - page.within(find('.board-blank-state')) do - click_button("Nevermind, I'll use my own") - end - expect(page).to have_selector('.board', count: 2) end it 'creates default lists' do lists = ['Open', 'To Do', 'Doing', 'Closed'] - page.within(find('.board-blank-state')) do - click_button('Add default lists') - end wait_for_requests expect(page).to have_selector('.board', count: 4) @@ -181,9 +159,7 @@ RSpec.describe 'Issue Boards', :js do end it 'allows user to delete board' do - page.within(find('.board:nth-child(2)')) do - accept_confirm { find('.board-delete').click } - end + remove_list wait_for_requests @@ -196,9 +172,7 @@ RSpec.describe 'Issue Boards', :js do find('.js-new-board-list').click - page.within(find('.board:nth-child(2)')) do - accept_confirm { find('.board-delete').click } - end + remove_list wait_for_requests @@ -692,4 +666,14 @@ RSpec.describe 'Issue Boards', :js do click_button(link_text) end end + + def remove_list + page.within(find('.board:nth-child(2)')) do + find('button[title="List settings"]').click + end + + page.within(find('.js-board-settings-sidebar')) do + accept_confirm { find('[data-testid="remove-list"]').click } + end + end end diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index 4b4cb444903..332c90df6d7 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -229,7 +229,7 @@ RSpec.describe 'Issue Boards', :js do end context 'time tracking' do - let(:compare_meter_tooltip) { find('.time-tracking .time-tracking-content .compare-meter')['data-original-title'] } + let(:compare_meter_tooltip) { find('.time-tracking .time-tracking-content .compare-meter')['title'] } before do issue2.timelogs.create(time_spent: 14400, user: user) diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb index 346f305f0d0..5f58fa420fb 100644 --- a/spec/features/calendar_spec.rb +++ b/spec/features/calendar_spec.rb @@ -180,7 +180,7 @@ RSpec.describe 'Contributions Calendar', :js do before do push_code_contribution - Timecop.freeze(Date.yesterday) do + travel_to(Date.yesterday) do Issues::CreateService.new(contributed_project, user, issue_params).execute end end diff --git a/spec/features/clusters/cluster_detail_page_spec.rb b/spec/features/clusters/cluster_detail_page_spec.rb index 4f7f62d00a5..31d6bcda9e8 100644 --- a/spec/features/clusters/cluster_detail_page_spec.rb +++ b/spec/features/clusters/cluster_detail_page_spec.rb @@ -87,6 +87,7 @@ RSpec.describe 'Clusterable > Show page' do within('#advanced-settings-section') do expect(page).to have_content('Google Kubernetes Engine') expect(page).to have_content('Manage your Kubernetes cluster by visiting') + expect_common_advanced_options end end end @@ -117,6 +118,7 @@ RSpec.describe 'Clusterable > Show page' do within('#advanced-settings-section') do expect(page).not_to have_content('Google Kubernetes Engine') expect(page).not_to have_content('Manage your Kubernetes cluster by visiting') + expect_common_advanced_options end end end @@ -176,4 +178,14 @@ RSpec.describe 'Clusterable > Show page' do let(:cluster) { create(:cluster, :provided_by_user, :instance) } end end + + private + + def expect_common_advanced_options + aggregate_failures do + expect(page).to have_content('Cluster management project') + expect(page).to have_content('Clear cluster cache') + expect(page).to have_content('Remove Kubernetes cluster integration') + end + end end diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index e66a40720da..97ee891dbb8 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -140,6 +140,7 @@ RSpec.describe 'Commits' do context 'when accessing internal project with disallowed access', :js do before do + stub_feature_flags(graphql_pipeline_header: false) project.update( visibility_level: Gitlab::VisibilityLevel::INTERNAL, public_builds: false) diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb index 7526a55a3c1..3cb7140d253 100644 --- a/spec/features/dashboard/issuables_counter_spec.rb +++ b/spec/features/dashboard/issuables_counter_spec.rb @@ -23,7 +23,7 @@ RSpec.describe 'Navigation bar counter', :use_clean_rails_memory_store_caching d user.invalidate_cache_counts - Timecop.travel(3.minutes.from_now) do + travel_to(3.minutes.from_now) do visit issues_path expect_counters('issues', '0') @@ -39,7 +39,7 @@ RSpec.describe 'Navigation bar counter', :use_clean_rails_memory_store_caching d user.invalidate_cache_counts - Timecop.travel(3.minutes.from_now) do + travel_to(3.minutes.from_now) do visit merge_requests_path expect_counters('merge_requests', '0') diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb index 5331b5559d8..952a78ec79a 100644 --- a/spec/features/dashboard/merge_requests_spec.rb +++ b/spec/features/dashboard/merge_requests_spec.rb @@ -19,6 +19,12 @@ RSpec.describe 'Dashboard Merge Requests' do sign_in(current_user) end + it 'disables target branch filter' do + visit merge_requests_dashboard_path + + expect(page).not_to have_selector('#js-dropdown-target-branch', visible: false) + end + context 'new merge request dropdown' do let(:project_with_disabled_merge_requests) { create(:project, :merge_requests_disabled) } diff --git a/spec/features/dashboard/todos/todos_filtering_spec.rb b/spec/features/dashboard/todos/todos_filtering_spec.rb index f60b07c976e..b1464af4194 100644 --- a/spec/features/dashboard/todos/todos_filtering_spec.rb +++ b/spec/features/dashboard/todos/todos_filtering_spec.rb @@ -130,6 +130,7 @@ RSpec.describe 'Dashboard > User filters todos', :js do before do create(:todo, :build_failed, user: user_1, author: user_2, project: project_1) create(:todo, :marked, user: user_1, author: user_2, project: project_1, target: issue1) + create(:todo, :review_requested, user: user_1, author: user_2, project: project_1, target: issue1) end it 'filters by Assigned' do @@ -138,6 +139,12 @@ RSpec.describe 'Dashboard > User filters todos', :js do expect_to_see_action(:assigned) end + it 'filters by Review Requested' do + filter_action('Review requested') + + expect_to_see_action(:review_requested) + end + it 'filters by Mentioned' do filter_action('Mentioned') @@ -168,6 +175,7 @@ RSpec.describe 'Dashboard > User filters todos', :js do def expect_to_see_action(action_name) action_names = { assigned: ' assigned you ', + review_requested: ' requested a review of ', mentioned: ' mentioned ', marked: ' added a todo for ', build_failed: ' build failed for ' diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb index cf773d2caed..0b4fed55f11 100644 --- a/spec/features/dashboard/todos/todos_spec.rb +++ b/spec/features/dashboard/todos/todos_spec.rb @@ -197,6 +197,21 @@ RSpec.describe 'Dashboard Todos' do end end end + + context 'review request todo' do + let(:merge_request) { create(:merge_request, title: "Fixes issue") } + + before do + create(:todo, :review_requested, user: user, project: project, target: merge_request, author: user) + visit dashboard_todos_path + end + + it 'shows you set yourself as an reviewer message' do + page.within('.js-todos-all') do + expect(page).to have_content("You requested a review of merge request #{merge_request.to_reference} \"Fixes issue\" at #{project.namespace.owner_name} / #{project.name} from yourself") + end + end + end end context 'User has done todos', :js do @@ -213,7 +228,7 @@ RSpec.describe 'Dashboard Todos' do describe 'restoring the todo' do before do within first('.todo') do - click_link 'Add a To Do' + click_link 'Add a to do' end end @@ -228,7 +243,7 @@ RSpec.describe 'Dashboard Todos' do end end - context 'User has Todos with labels spanning multiple projects' do + context 'User has to dos with labels spanning multiple projects' do before do label1 = create(:label, project: project) note1 = create(:note_on_issue, note: "Hello #{label1.to_reference(format: :name)}", noteable_id: issue.id, noteable_type: 'Issue', project: issue.project) diff --git a/spec/features/discussion_comments/snippets_spec.rb b/spec/features/discussion_comments/snippets_spec.rb index 50201bbdb21..b2d3fbf4b5d 100644 --- a/spec/features/discussion_comments/snippets_spec.rb +++ b/spec/features/discussion_comments/snippets_spec.rb @@ -8,7 +8,6 @@ RSpec.describe 'Thread Comments Snippet', :js do let_it_be(:snippet) { create(:project_snippet, :private, :repository, project: project, author: user) } before do - stub_feature_flags(snippets_vue: false) project.add_maintainer(user) sign_in(user) diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb index e705f2916da..49343cc7a57 100644 --- a/spec/features/expand_collapse_diffs_spec.rb +++ b/spec/features/expand_collapse_diffs_spec.rb @@ -7,6 +7,7 @@ RSpec.describe 'Expand and collapse diffs', :js do let(:project) { create(:project, :repository) } before do + stub_feature_flags(increased_diff_limits: false) sign_in(create(:admin)) # Ensure that undiffable.md is in .gitattributes diff --git a/spec/features/file_uploads/maven_package_spec.rb b/spec/features/file_uploads/maven_package_spec.rb index c873a0e9a36..e87eec58618 100644 --- a/spec/features/file_uploads/maven_package_spec.rb +++ b/spec/features/file_uploads/maven_package_spec.rb @@ -25,5 +25,31 @@ RSpec.describe 'Upload a maven package', :api, :js do it { expect(subject.code).to eq(200) } end + RSpec.shared_examples 'for a maven sha1' do + let(:dummy_package) { double(Packages::Package) } + let(:api_path) { "/projects/#{project.id}/packages/maven/com/example/my-app/1.0/my-app-1.0-20180724.124855-1.jar.sha1" } + + before do + # The sha verification done by the maven api is between: + # - the sha256 set by workhorse + # - the sha256 of the sha1 of the uploaded package file + # We're going to send `file` for the sha1 and stub the sha1 of the package file so that + # both sha256 being the same + expect(::Packages::PackageFileFinder).to receive(:new).and_return(double(execute!: dummy_package)) + expect(dummy_package).to receive(:file_sha1).and_return(File.read(file.path)) + end + + it { expect(subject.code).to eq(204) } + end + + RSpec.shared_examples 'for a maven md5' do + let(:api_path) { "/projects/#{project.id}/packages/maven/com/example/my-app/1.0/my-app-1.0-20180724.124855-1.jar.md5" } + let(:file) { StringIO.new('dummy_package') } + + it { expect(subject.code).to eq(200) } + end + it_behaves_like 'handling file uploads', 'for a maven package' + it_behaves_like 'handling file uploads', 'for a maven sha1' + it_behaves_like 'handling file uploads', 'for a maven md5' end diff --git a/spec/features/groups/clusters/eks_spec.rb b/spec/features/groups/clusters/eks_spec.rb index 5a62741250a..c361c502cbb 100644 --- a/spec/features/groups/clusters/eks_spec.rb +++ b/spec/features/groups/clusters/eks_spec.rb @@ -19,7 +19,7 @@ RSpec.describe 'Group AWS EKS Cluster', :js do before do visit group_clusters_path(group) - click_link 'Add Kubernetes cluster' + click_link 'Integrate with a cluster certificate' end context 'when user creates a cluster on AWS EKS' do diff --git a/spec/features/groups/clusters/user_spec.rb b/spec/features/groups/clusters/user_spec.rb index 90253451d6b..97f8864aab2 100644 --- a/spec/features/groups/clusters/user_spec.rb +++ b/spec/features/groups/clusters/user_spec.rb @@ -25,7 +25,7 @@ RSpec.describe 'User Cluster', :js do before do visit group_clusters_path(group) - click_link 'Add Kubernetes cluster' + click_link 'Integrate with a cluster certificate' click_link 'Connect existing cluster' end @@ -66,6 +66,10 @@ RSpec.describe 'User Cluster', :js do expect(page.find_field('cluster[platform_kubernetes_attributes][authorization_type]', disabled: true)).to be_checked end end + + it 'user sees namespace per environment is enabled by default' do + expect(page).to have_checked_field('Namespace per environment') + end end context 'when user filled form with invalid parameters' do @@ -125,7 +129,7 @@ RSpec.describe 'User Cluster', :js do it 'user sees creation form with the successful message' do expect(page).to have_content('Kubernetes cluster integration was successfully removed.') - expect(page).to have_link('Add Kubernetes cluster') + expect(page).to have_link('Integrate with a cluster certificate') end end end diff --git a/spec/features/groups/members/leave_group_spec.rb b/spec/features/groups/members/leave_group_spec.rb index 9eb5cc15c5e..32acf7edd2a 100644 --- a/spec/features/groups/members/leave_group_spec.rb +++ b/spec/features/groups/members/leave_group_spec.rb @@ -70,7 +70,7 @@ RSpec.describe 'Groups > Members > Leave group' do visit group_group_members_path(group) - expect(find(:css, '.project-members-page li', text: user.name)).not_to have_selector(:css, 'a.btn-remove') + expect(find(:css, '.project-members-page li', text: user.name)).to have_no_selector(:css, 'a.btn-danger') end it 'owner can not leave the group by url param if they are the last owner', :js do diff --git a/spec/features/groups/members/manage_groups_spec.rb b/spec/features/groups/members/manage_groups_spec.rb index e3bbbd4d73b..33caa3af36d 100644 --- a/spec/features/groups/members/manage_groups_spec.rb +++ b/spec/features/groups/members/manage_groups_spec.rb @@ -6,62 +6,115 @@ RSpec.describe 'Groups > Members > Manage groups', :js do include Select2Helper include Spec::Support::Helpers::Features::ListRowsHelpers - let(:user) { create(:user) } - let(:shared_with_group) { create(:group) } - let(:shared_group) { create(:group) } + let_it_be(:user) { create(:user) } before do stub_feature_flags(vue_group_members_list: false) - shared_group.add_owner(user) sign_in(user) end - it 'add group to group' do - visit group_group_members_path(shared_group) + context 'when group link does not exist' do + let_it_be(:group) { create(:group) } + let_it_be(:group_to_add) { create(:group) } - add_group(shared_with_group.id, 'Reporter') + before do + group.add_owner(user) + visit group_group_members_path(group) + end - click_groups_tab + it 'add group to group' do + add_group(group_to_add.id, 'Reporter') - page.within(first_row) do - expect(page).to have_content(shared_with_group.name) - expect(page).to have_content('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 - it 'remove group from group' do - create(:group_group_link, shared_group: shared_group, - shared_with_group: shared_with_group, group_access: ::Gitlab::Access::DEVELOPER) + context 'when group link exists' do + let_it_be(:shared_with_group) { create(:group) } + let_it_be(:shared_group) { create(:group) } - visit group_group_members_path(shared_group) + let(:additional_link_attrs) { {} } - click_groups_tab + let_it_be(:group_link, refind: true) do + create( + :group_group_link, + shared_group: shared_group, + shared_with_group: shared_with_group, + group_access: ::Gitlab::Access::DEVELOPER + ) + end - expect(page).to have_content(shared_with_group.name) + before do + travel_to Time.now.utc.beginning_of_day + group_link.update!(additional_link_attrs) - accept_confirm do - find(:css, '#tab-groups li', text: shared_with_group.name).find(:css, 'a.btn-remove').click + shared_group.add_owner(user) + visit group_group_members_path(shared_group) end - expect(page).not_to have_content(shared_with_group.name) - end + it 'remove group from group' do + click_groups_tab + + expect(page).to have_content(shared_with_group.name) + + accept_confirm do + find(:css, '#tab-groups li', text: shared_with_group.name).find(:css, 'a.btn-danger').click + end + + expect(page).not_to have_content(shared_with_group.name) + end - it 'update group to owner level' do - create(:group_group_link, shared_group: shared_group, - shared_with_group: shared_with_group, group_access: ::Gitlab::Access::DEVELOPER) + it 'update group to owner level' do + click_groups_tab - visit group_group_members_path(shared_group) + page.within(first_row) do + click_button('Developer') + click_link('Maintainer') - click_groups_tab + wait_for_requests - page.within(first_row) do - click_button('Developer') - click_link('Maintainer') + expect(page).to have_button('Maintainer') + end + end + + it 'updates expiry date' do + click_groups_tab + + expires_at_field = "member_expires_at_#{shared_with_group.id}" + fill_in "member_expires_at_#{shared_with_group.id}", with: 3.days.from_now.to_date + find_field(expires_at_field).native.send_keys :enter wait_for_requests - expect(page).to have_button('Maintainer') + page.within(find('li.group_member')) do + expect(page).to have_content('Expires in 3 days') + end + end + + context 'when expiry date is set' do + let(:additional_link_attrs) { { expires_at: 3.days.from_now.to_date } } + + it 'clears expiry date' do + click_groups_tab + + page.within(find('li.group_member')) do + expect(page).to have_content('Expires in 3 days') + + page.within(find('.js-edit-member-form')) do + find('.js-clear-input').click + end + + wait_for_requests + + expect(page).not_to have_content('Expires in') + 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 d94cc85f411..dd708c243a8 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 @@ -6,65 +6,66 @@ RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js include Select2Helper include ActiveSupport::Testing::TimeHelpers - let(:user1) { create(:user, name: 'John Doe') } - let!(:new_member) { create(:user, name: 'Mary Jane') } - let(:group) { create(:group) } + let_it_be(:user1) { create(:user, name: 'John Doe') } + let_it_be(:group) { create(:group) } + let(:new_member) { create(:user, name: 'Mary Jane') } before do stub_feature_flags(vue_group_members_list: false) + travel_to Time.now.utc.beginning_of_day + group.add_owner(user1) sign_in(user1) end it 'expiration date is displayed in the members list' do - travel_to Time.zone.parse('2016-08-06 08:00') do - date = 4.days.from_now - 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: date.to_s(:medium) + "\n" - click_on 'Invite' - end - - page.within "#group_member_#{group_member_id(new_member)}" do - expect(page).to have_content('Expires in 4 days') - end + 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: 3.days.from_now.to_date + find_field('expires_at').native.send_keys :enter + + click_on 'Invite' + end + + page.within "#group_member_#{group_member_id}" do + expect(page).to have_content('Expires in 3 days') end end - it 'change expiration date' do - travel_to Time.zone.parse('2016-08-06 08:00') do - date = 3.days.from_now - group.add_developer(new_member) + it 'changes expiration date' do + group.add_developer(new_member) + visit group_group_members_path(group) + + page.within "#group_member_#{group_member_id}" do + fill_in 'Expiration date', with: 3.days.from_now.to_date + find_field('Expiration date').native.send_keys :enter - visit group_group_members_path(group) + wait_for_requests - page.within "#group_member_#{group_member_id(new_member)}" do - find('.js-access-expiration-date').set date.to_s(:medium) + "\n" - wait_for_requests - expect(page).to have_content('Expires in 3 days') - end + expect(page).to have_content('Expires in 3 days') end end - it 'remove expiration date' do - travel_to Time.zone.parse('2016-08-06 08:00') do - date = 3.days.from_now - group_member = create(:group_member, :developer, user: new_member, group: group, expires_at: date.to_s(:medium)) + it 'clears expiration date' do + create(:group_member, :developer, user: new_member, group: group, expires_at: 3.days.from_now.to_date) + visit group_group_members_path(group) + + page.within "#group_member_#{group_member_id}" do + expect(page).to have_content('Expires in 3 days') + + find('.js-clear-input').click - visit group_group_members_path(group) + wait_for_requests - page.within "#group_member_#{group_member.id}" do - find('.js-clear-input').click - wait_for_requests - expect(page).not_to have_content('Expires in 3 days') - end + expect(page).not_to have_content('Expires in') end end - def group_member_id(user) + def group_member_id group.members.find_by(user_id: new_member).id end end diff --git a/spec/features/groups/navbar_spec.rb b/spec/features/groups/navbar_spec.rb index 60f1c404e78..e81f2370d10 100644 --- a/spec/features/groups/navbar_spec.rb +++ b/spec/features/groups/navbar_spec.rb @@ -72,4 +72,12 @@ RSpec.describe 'Group navbar' do it_behaves_like 'verified navigation bar' end + + context 'when invite team members is not available' do + it 'does not display the js-invite-members-trigger' do + visit group_path(group) + + expect(page).not_to have_selector('.js-invite-members-trigger') + end + end end diff --git a/spec/features/groups/packages_spec.rb b/spec/features/groups/packages_spec.rb index d81e4aa70cf..60e0c08b3d4 100644 --- a/spec/features/groups/packages_spec.rb +++ b/spec/features/groups/packages_spec.rb @@ -48,7 +48,7 @@ RSpec.describe 'Group Packages' do it 'allows you to navigate to the project page' do page.within('[data-qa-selector="packages-table"]') do - click_link project.name + find('[data-qa-selector="package-path"]', text: project.name).click end expect(page).to have_current_path(project_path(project)) diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb index ec30f34199d..304573ecd6e 100644 --- a/spec/features/groups/show_spec.rb +++ b/spec/features/groups/show_spec.rb @@ -184,4 +184,17 @@ RSpec.describe 'Group show page' do expect(page).to have_selector('.notifications-btn.disabled', visible: true) end end + + context 'page og:description' do + let(:group) { create(:group, description: '**Lorem** _ipsum_ dolor sit [amet](https://example.com)') } + let(:maintainer) { create(:user) } + + before do + group.add_maintainer(maintainer) + sign_in(maintainer) + visit path + end + + it_behaves_like 'page meta description', 'Lorem ipsum dolor sit amet' + end end diff --git a/spec/features/incidents/incident_details_spec.rb b/spec/features/incidents/incident_details_spec.rb new file mode 100644 index 00000000000..3ec7717b649 --- /dev/null +++ b/spec/features/incidents/incident_details_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Incident details', :js do + let_it_be(:project) { create(:project) } + let_it_be(:developer) { create(:user) } + let_it_be(:incident) { create(:incident, project: project, author: developer, description: 'description') } + + before_all do + project.add_developer(developer) + end + + before do + sign_in(developer) + + visit project_issues_incident_path(project, incident) + wait_for_requests + end + + context 'when a developer+ displays the incident' do + it 'shows the incident' do + page.within('.issuable-details') do + expect(find('h2')).to have_content(incident.title) + end + end + + it 'does not show design management' do + expect(page).not_to have_selector('.js-design-management') + end + + it 'shows the incident tabs' do + page.within('.issuable-details') do + incident_tabs = find('[data-testid="incident-tabs"]') + + expect(find('h2')).to have_content(incident.title) + expect(incident_tabs).to have_content('Summary') + expect(incident_tabs).to have_content(incident.description) + end + end + + it 'shows the right sidebar mounted with type issue' do + page.within('.layout-page') do + sidebar = find('.right-sidebar') + + expect(page).to have_selector('.right-sidebar[data-issuable-type="issue"]') + expect(sidebar).to have_selector('.incident-severity') + expect(sidebar).not_to have_selector('.milestone') + end + end + end +end diff --git a/spec/features/incidents/incidents_list_spec.rb b/spec/features/incidents/incidents_list_spec.rb new file mode 100644 index 00000000000..c65c83b2804 --- /dev/null +++ b/spec/features/incidents/incidents_list_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Incident Management index', :js do + let_it_be(:project) { create(:project) } + let_it_be(:developer) { create(:user) } + let_it_be(:incident) { create(:incident, project: project) } + + before_all do + project.add_developer(developer) + end + + before do + sign_in(developer) + + visit project_incidents_path(project) + wait_for_requests + end + + context 'when a developer displays the incident list' do + it 'shows the status tabs' do + expect(page).to have_selector('.gl-tabs') + end + + it 'shows the filtered search' do + expect(page).to have_selector('.filtered-search-wrapper') + end + + it 'shows the alert table' do + expect(page).to have_selector('.gl-table') + end + + it 'alert page title' do + expect(page).to have_content('Incidents') + end + end +end diff --git a/spec/features/incidents/user_creates_new_incident_spec.rb b/spec/features/incidents/user_creates_new_incident_spec.rb new file mode 100644 index 00000000000..99a137b5852 --- /dev/null +++ b/spec/features/incidents/user_creates_new_incident_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Incident Management index', :js do + let_it_be(:project) { create(:project) } + let_it_be(:developer) { create(:user) } + let_it_be(:guest) { create(:user) } + let_it_be(:incident) { create(:incident, project: project) } + + before_all do + project.add_developer(developer) + project.add_guest(guest) + end + + shared_examples 'create incident form' do + it 'shows the create new issue button' do + expect(page).to have_selector('.create-incident-button') + end + + it 'when clicked shows the create issue page with the Incident type pre-selected' do + find('.create-incident-button').click + wait_for_all_requests + + expect(page).to have_selector('.dropdown-menu-toggle') + expect(page).to have_selector('.js-issuable-type-filter-dropdown-wrap') + + page.within('.js-issuable-type-filter-dropdown-wrap') do + expect(page).to have_content('Incident') + end + end + end + + context 'when a developer displays the incident list' do + before do + sign_in(developer) + + visit project_incidents_path(project) + wait_for_all_requests + end + + it_behaves_like 'create incident form' + end + + context 'when a guest displays the incident list' do + before do + sign_in(guest) + + visit project_incidents_path(project) + wait_for_all_requests + end + + it_behaves_like 'create incident form' + end +end diff --git a/spec/features/incidents/user_filters_incidents_by_status_spec.rb b/spec/features/incidents/user_filters_incidents_by_status_spec.rb new file mode 100644 index 00000000000..661c737141b --- /dev/null +++ b/spec/features/incidents/user_filters_incidents_by_status_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User filters Incident Management table by status', :js do + let_it_be(:project) { create(:project) } + let_it_be(:developer) { create(:user) } + + before_all do + project.add_developer(developer) + + create_list(:incident, 2, project: project, state: 'opened') + create(:incident, project: project, state: 'closed') + end + + before do + sign_in(developer) + + visit project_incidents_path(project) + wait_for_requests + end + + context 'when a developer displays the incident list they can filter the table by an incident status' do + def the_page_shows_the_nav_text_with_correct_count + expect(page).to have_selector('.gl-table') + expect(page).to have_content('All 3') + expect(page).to have_content('Open 2') + expect(page).to have_content('Closed 1') + end + + it 'shows the incident table items with incident status of Open by default' do + expect(find('.gl-tab-nav-item-active')).to have_content('Open 2') + expect(all('tbody tr').count).to be(2) + + the_page_shows_the_nav_text_with_correct_count + end + + it 'shows the incident table items with incident status of Closed' do + find('.gl-tab-nav-item', text: 'Closed').click + wait_for_requests + + expect(find('.gl-tab-nav-item-active')).to have_content('Closed 1') + expect(all('tbody tr').count).to be(1) + + the_page_shows_the_nav_text_with_correct_count + end + + it 'shows the incident table items with all status' do + find('.gl-tab-nav-item', text: 'All').click + wait_for_requests + + expect(find('.gl-tab-nav-item-active')).to have_content('All 3') + expect(all('[data-testid="incident-assignees"]').count).to be(3) + expect(all('tbody tr').count).to be(3) + + the_page_shows_the_nav_text_with_correct_count + end + end +end diff --git a/spec/features/incidents/user_searches_incidents_spec.rb b/spec/features/incidents/user_searches_incidents_spec.rb new file mode 100644 index 00000000000..b8e3ff534c3 --- /dev/null +++ b/spec/features/incidents/user_searches_incidents_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User searches Incident Management incidents', :js do + let_it_be(:project) { create(:project) } + let_it_be(:developer) { create(:user) } + let_it_be(:incident) { create(:incident, project: project) } + + before_all do + project.add_developer(developer) + end + + before do + sign_in(developer) + + visit project_incidents_path(project) + wait_for_requests + end + + context 'when a developer displays the incident list they can search for an incident' do + it 'shows the incident table with an incident for a valid search filter bar' do + expect(page).to have_selector('.filtered-search-wrapper') + expect(page).to have_selector('.gl-table') + expect(page).to have_selector('.incident-severity') + expect(all('tbody tr').count).to be(1) + expect(page).not_to have_selector('.empty-state') + end + end +end diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb index 3954de56eea..8ccaf82536a 100644 --- a/spec/features/invites_spec.rb +++ b/spec/features/invites_spec.rb @@ -23,7 +23,8 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do end def fill_in_sign_up_form(new_user) - fill_in 'new_user_name', with: new_user.name + 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 @@ -81,10 +82,10 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do end end - context 'when inviting a user using their email address' do + context 'when inviting a user' 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) } + 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) } context 'when user has not signed in yet' do @@ -210,30 +211,43 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do context 'when declining the invitation' do let(:send_email_confirmation) { true } - context 'when signed in' do - before do - sign_in(user) - visit invite_path(group_invite.raw_invite_token) + context 'as an existing user' do + let(:group_invite) { create(:group_member, user: user, group: group, created_by: owner) } + + 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 - it 'declines application and redirects to dashboard' do - page.click_link 'Decline' + context 'when signed out' do + before do + visit decline_invite_path(group_invite.raw_invite_token) + end - 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 + 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 end end - context 'when signed out' do + context 'as a non-existing user' do before do visit decline_invite_path(group_invite.raw_invite_token) end - 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.') + 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 diff --git a/spec/features/issuables/close_reopen_report_toggle_spec.rb b/spec/features/issuables/close_reopen_report_toggle_spec.rb index 5ea89a7984f..6e99cfb3293 100644 --- a/spec/features/issuables/close_reopen_report_toggle_spec.rb +++ b/spec/features/issuables/close_reopen_report_toggle_spec.rb @@ -23,7 +23,15 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do expect(container).to have_content("Close #{human_model_name}") expect(container).to have_content('Report abuse') expect(container).to have_content("Report #{human_model_name.pluralize} that are abusive, inappropriate or spam.") - expect(container).to have_selector('.close-item.droplab-item-selected') + + if issuable.is_a?(MergeRequest) + page.within('.js-issuable-close-dropdown') do + expect(page).to have_link('Close merge request') + end + else + expect(container).to have_selector('.close-item.droplab-item-selected') + end + expect(container).to have_selector('.report-item') expect(container).not_to have_selector('.report-item.droplab-item-selected') expect(container).not_to have_selector('.reopen-item') @@ -123,7 +131,7 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do it 'shows only the `Edit` button' do expect(page).to have_link('Edit') - expect(page).not_to have_link('Report abuse') + expect(page).to have_link('Report abuse') expect(page).not_to have_button('Close merge request') expect(page).not_to have_button('Reopen merge request') end diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb index b1ffaaa7c7e..3f00bdc478d 100644 --- a/spec/features/issuables/issuable_list_spec.rb +++ b/spec/features/issuables/issuable_list_spec.rb @@ -48,6 +48,14 @@ RSpec.describe 'issuable list', :js do end end + it 'displays a warning if counting the number of issues times out' do + allow_any_instance_of(IssuesFinder).to receive(:count_by_state).and_raise(ActiveRecord::QueryCanceled) + + visit_issuable_list(:issue) + + expect(page).to have_text('Open ? Closed ? All ?') + end + it "counts merge requests closing issues icons for each issue" do visit_issuable_list(:issue) diff --git a/spec/features/issuables/markdown_references/internal_references_spec.rb b/spec/features/issuables/markdown_references/internal_references_spec.rb index aceaea8d2ed..07d4271eed7 100644 --- a/spec/features/issuables/markdown_references/internal_references_spec.rb +++ b/spec/features/issuables/markdown_references/internal_references_spec.rb @@ -25,7 +25,7 @@ RSpec.describe "Internal references", :js do add_note("##{public_project_issue.to_reference(private_project)}") end - context "when user doesn't have access to private project" do + context "when user doesn't have access to private project", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/257832' do before do sign_in(public_project_user) @@ -52,7 +52,7 @@ RSpec.describe "Internal references", :js do visit(project_issue_path(public_project, public_project_issue)) end - it "doesn't show any references" do + it "doesn't show any references", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/257832' do page.within(".issue-details") do expect(page).not_to have_content("#merge-requests .merge-requests-title") end @@ -94,7 +94,7 @@ RSpec.describe "Internal references", :js do add_note("##{public_project_merge_request.to_reference(private_project)}") end - context "when user doesn't have access to private project" do + context "when user doesn't have access to private project", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/257832' do before do sign_in(public_project_user) @@ -121,7 +121,7 @@ RSpec.describe "Internal references", :js do visit(project_merge_request_path(public_project, public_project_merge_request)) end - it "doesn't show any references" do + it "doesn't show any references", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/257832' do page.within(".merge-request-details") do expect(page).not_to have_content("#merge-requests .merge-requests-title") end diff --git a/spec/features/issuables/merge_request_discussion_lock_spec.rb b/spec/features/issuables/merge_request_discussion_lock_spec.rb new file mode 100644 index 00000000000..4e0265839f6 --- /dev/null +++ b/spec/features/issuables/merge_request_discussion_lock_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true +# TODO use shared examples to merge this spec with discussion_lock_spec.rb +# https://gitlab.com/gitlab-org/gitlab/-/issues/255910 + +require 'spec_helper' + +RSpec.describe 'Merge Request Discussion Lock', :js do + let(:user) { create(:user) } + let(:project) { create(:project, :public, :repository) } + let(:merge_request) { create(:merge_request, source_project: project, author: user) } + + before do + sign_in(user) + end + + context 'when a user is a team member' do + before do + project.add_developer(user) + end + + context 'when the discussion is unlocked' do + it 'the user can lock the merge_request' do + visit project_merge_request_path(merge_request.project, merge_request) + + expect(find('.issuable-sidebar')).to have_content('Unlocked') + + page.within('.issuable-sidebar') do + find('.lock-edit').click + click_button('Lock') + end + + expect(find('[data-testid="lock-status"]')).to have_content('Locked') + end + end + + context 'when the discussion is locked' do + before do + merge_request.update_attribute(:discussion_locked, true) + visit project_merge_request_path(merge_request.project, merge_request) + end + + it 'the user can unlock the merge_request' do + expect(find('.issuable-sidebar')).to have_content('Locked') + + page.within('.issuable-sidebar') do + find('.lock-edit').click + click_button('Unlock') + end + + expect(find('[data-testid="lock-status"]')).to have_content('Unlocked') + end + end + end + + context 'when a user is not a team member' do + context 'when the discussion is unlocked' do + before do + visit project_merge_request_path(merge_request.project, merge_request) + end + + it 'the user can not lock the merge_request' do + expect(find('.issuable-sidebar')).to have_content('Unlocked') + expect(find('.issuable-sidebar')).not_to have_selector('.lock-edit') + end + end + + context 'when the discussion is locked' do + before do + merge_request.update_attribute(:discussion_locked, true) + visit project_merge_request_path(merge_request.project, merge_request) + end + + it 'the user can not unlock the merge_request' do + expect(find('.issuable-sidebar')).to have_content('Locked') + expect(find('.issuable-sidebar')).not_to have_selector('.lock-edit') + end + end + end +end diff --git a/spec/features/issues/csv_spec.rb b/spec/features/issues/csv_spec.rb index 8d06bf24f8b..c93693ec40a 100644 --- a/spec/features/issues/csv_spec.rb +++ b/spec/features/issues/csv_spec.rb @@ -31,13 +31,13 @@ RSpec.describe 'Issues csv' do end it 'triggers an email export' do - expect(ExportCsvWorker).to receive(:perform_async).with(user.id, project.id, hash_including("project_id" => project.id)) + expect(IssuableExportCsvWorker).to receive(:perform_async).with(:issue, user.id, project.id, hash_including("project_id" => project.id)) request_csv end it "doesn't send request params to ExportCsvWorker" do - expect(ExportCsvWorker).to receive(:perform_async).with(anything, anything, hash_excluding("controller" => anything, "action" => anything)) + expect(IssuableExportCsvWorker).to receive(:perform_async).with(:issue, anything, anything, hash_excluding("controller" => anything, "action" => anything)) request_csv end diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 0165fba9ace..ff78b9e608f 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -6,7 +6,9 @@ RSpec.describe 'GFM autocomplete', :js do let_it_be(:user_xss_title) { 'eve <img src=x onerror=alert(2)<img src=x onerror=alert(1)>' } let_it_be(:user_xss) { create(:user, name: user_xss_title, username: 'xss.user') } let_it_be(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') } - let_it_be(:project) { create(:project) } + let_it_be(:group) { create(:group, name: 'Ancestor') } + let_it_be(:child_group) { create(:group, parent: group, name: 'My group') } + let_it_be(:project) { create(:project, group: child_group) } let_it_be(:label) { create(:label, project: project, title: 'special+') } let(:issue) { create(:issue, project: project) } @@ -530,7 +532,7 @@ RSpec.describe 'GFM autocomplete', :js do expect(page).to have_selector('.tribute-container', visible: true) - expect(find('.tribute-container ul', visible: true).text).to have_content(user_xss.username) + expect(find('.tribute-container ul', visible: true)).to have_text(user_xss.username) end it 'selects the first item for assignee dropdowns' do @@ -558,6 +560,24 @@ RSpec.describe 'GFM autocomplete', :js do expect(find('.tribute-container ul', visible: true)).to have_content(user.name) end + context 'when autocompleting for groups' do + it 'shows the group when searching for the name of the group' do + page.within '.timeline-content-form' do + find('#note-body').native.send_keys('@mygroup') + end + + expect(find('.tribute-container ul', visible: true)).to have_text('My group') + end + + it 'does not show the group when searching for the name of the parent of the group' do + page.within '.timeline-content-form' do + find('#note-body').native.send_keys('@ancestor') + end + + expect(find('.tribute-container ul', visible: true)).not_to have_text('My group') + end + end + context 'if a selected value has special characters' do it 'wraps the result in double quotes' do note = find('#note-body') diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb index 38d11ee2560..94a1de06488 100644 --- a/spec/features/issues/issue_sidebar_spec.rb +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -16,129 +16,83 @@ RSpec.describe 'Issue Sidebar' do sign_in(user) end - context 'assignee', :js do + context 'when concerning the assignee', :js do let(:user2) { create(:user) } let(:issue2) { create(:issue, project: project, author: user2) } - context 'when invite_members_version_a experiment is enabled' do - before do - stub_experiment_for_user(invite_members_version_a: true) - end + include_examples 'issuable invite members experiments' do + let(:issuable_path) { project_issue_path(project, issue2) } + end - context 'when user can not see invite members' do - before do - project.add_developer(user) - visit_issue(project, issue2) + context 'when user is a developer' do + before do + project.add_developer(user) + visit_issue(project, issue2) - find('.block.assignee .edit-link').click + find('.block.assignee .edit-link').click - wait_for_requests - end + wait_for_requests + end - it 'does not see link to invite members' do - page.within '.dropdown-menu-user' do - expect(page).not_to have_link('Invite Members') - end + it 'shows author in assignee dropdown' do + page.within '.dropdown-menu-user' do + expect(page).to have_content(user2.name) end end - context 'when user can see invite members' do - before do - project.add_maintainer(user) - visit_issue(project, issue2) - - find('.block.assignee .edit-link').click + it 'shows author when filtering assignee dropdown' do + page.within '.dropdown-menu-user' do + find('.dropdown-input-field').set(user2.name) wait_for_requests - end - it 'sees link to invite members' do - page.within '.dropdown-menu-user' do - expect(page).to have_link('Invite Members', href: project_project_members_path(project)) - expect(page).to have_selector('[data-track-event="click_invite_members"]') - expect(page).to have_selector("[data-track-label='edit_assignee']") - end + expect(page).to have_content(user2.name) end end - end - - context 'when invite_members_version_a experiment is not enabled' do - context 'when user is a developer' do - before do - project.add_developer(user) - visit_issue(project, issue2) - find('.block.assignee .edit-link').click + it 'assigns yourself' do + find('.block.assignee .dropdown-menu-toggle').click - wait_for_requests - end - - it 'shows author in assignee dropdown' do - page.within '.dropdown-menu-user' do - expect(page).to have_content(user2.name) - end - end + click_button 'assign yourself' - it 'shows author when filtering assignee dropdown' do - page.within '.dropdown-menu-user' do - find('.dropdown-input-field').native.send_keys user2.name - sleep 1 # Required to wait for end of input delay + wait_for_requests - wait_for_requests + find('.block.assignee .edit-link').click - expect(page).to have_content(user2.name) - end + page.within '.dropdown-menu-user' do + expect(page.find('.dropdown-header')).to be_visible + expect(page.find('.dropdown-menu-user-link.is-active')).to have_content(user.name) end + end - it 'assigns yourself' do - find('.block.assignee .dropdown-menu-toggle').click - - click_button 'assign yourself' - - wait_for_requests + it 'keeps your filtered term after filtering and dismissing the dropdown' do + find('.dropdown-input-field').set(user2.name) - find('.block.assignee .edit-link').click + wait_for_requests - page.within '.dropdown-menu-user' do - expect(page.find('.dropdown-header')).to be_visible - expect(page.find('.dropdown-menu-user-link.is-active')).to have_content(user.name) - end + page.within '.dropdown-menu-user' do + expect(page).not_to have_content 'Unassigned' + click_link user2.name end - it 'keeps your filtered term after filtering and dismissing the dropdown' do - find('.dropdown-input-field').native.send_keys user2.name - - wait_for_requests - - page.within '.dropdown-menu-user' do - expect(page).not_to have_content 'Unassigned' - click_link user2.name - end - - find('.js-right-sidebar').click - find('.block.assignee .edit-link').click + find('.js-right-sidebar').click + find('.block.assignee .edit-link').click - expect(page.all('.dropdown-menu-user li').length).to eq(1) - expect(find('.dropdown-input-field').value).to eq(user2.name) - end + expect(page.all('.dropdown-menu-user li').length).to eq(1) + expect(find('.dropdown-input-field').value).to eq(user2.name) end + end - context 'when user is a maintainer' do - before do - project.add_maintainer(user) - visit_issue(project, issue2) + it 'shows label text as "Apply" when assignees are changed' do + project.add_developer(user) + visit_issue(project, issue2) - find('.block.assignee .edit-link').click + find('.block.assignee .edit-link').click + wait_for_requests - wait_for_requests - end + click_on 'Unassigned' - it 'shows author in assignee dropdown and no invite link' do - page.within '.dropdown-menu-user' do - expect(page).not_to have_link('Invite Members') - end - end - end + expect(page).to have_link('Apply') end end diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb index 3de33049db0..315d1c911a2 100644 --- a/spec/features/issues/todo_spec.rb +++ b/spec/features/issues/todo_spec.rb @@ -15,7 +15,7 @@ RSpec.describe 'Manually create a todo item from issue', :js do it 'creates todo when clicking button' do page.within '.issuable-sidebar' do - click_button 'Add a To Do' + click_button 'Add a to do' expect(page).to have_content 'Mark as done' end @@ -32,7 +32,7 @@ RSpec.describe 'Manually create a todo item from issue', :js do it 'marks a todo as done' do page.within '.issuable-sidebar' do - click_button 'Add a To Do' + click_button 'Add a to do' click_button 'Mark as done' end diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb index 88b8e9624e2..de746415205 100644 --- a/spec/features/issues/user_edits_issue_spec.rb +++ b/spec/features/issues/user_edits_issue_spec.rb @@ -6,292 +6,356 @@ RSpec.describe "Issues > User edits issue", :js do let_it_be(:project) { create(:project_empty_repo, :public) } let_it_be(:project_with_milestones) { create(:project_empty_repo, :public) } let_it_be(:user) { create(:user) } - let_it_be(:issue) { create(:issue, project: project, author: user, assignees: [user]) } + let_it_be(:label_assigned) { create(:label, project: project, title: 'verisimilitude') } + let_it_be(:label_unassigned) { create(:label, project: project, title: 'syzygy') } + let_it_be(:issue) { create(:issue, project: project, author: user, assignees: [user], labels: [label_assigned]) } let_it_be(:issue_with_milestones) { create(:issue, project: project_with_milestones, author: user, assignees: [user]) } - let_it_be(:label) { create(:label, project: project) } let_it_be(:milestone) { create(:milestone, project: project) } let_it_be(:milestones) { create_list(:milestone, 25, project: project_with_milestones) } - before do - project.add_developer(user) - project_with_milestones.add_developer(user) - sign_in(user) - end - - context "from edit page" do + context 'with authorized user' do before do - visit edit_project_issue_path(project, issue) + project.add_developer(user) + project_with_milestones.add_developer(user) + sign_in(user) end - it "previews content" do - form = first(".gfm-form") - - page.within(form) do - fill_in("Description", with: "Bug fixed :smile:") - click_button("Preview") + context "from edit page" do + before do + visit edit_project_issue_path(project, issue) end - expect(form).to have_button("Write") - end - - it 'allows user to select unassigned' do - visit edit_project_issue_path(project, issue) - - expect(page).to have_content "Assignee #{user.name}" + it "previews content" do + form = first(".gfm-form") - first('.js-user-search').click - click_link 'Unassigned' - - click_button 'Save changes' + page.within(form) do + fill_in("Description", with: "Bug fixed :smile:") + click_button("Preview") + end - page.within('.assignee') do - expect(page).to have_content 'None - assign yourself' + expect(form).to have_button("Write") end - end - context 'with due date' do - before do + it 'allows user to select unassigned' do visit edit_project_issue_path(project, issue) - end - - it 'saves with due date' do - date = Date.today.at_beginning_of_month.tomorrow - fill_in 'issue_title', with: 'bug 345' - fill_in 'issue_description', with: 'bug description' - find('#issuable-due-date').click - - page.within '.pika-single' do - click_button date.day - end + expect(page).to have_content "Assignee #{user.name}" - expect(find('#issuable-due-date').value).to eq date.to_s + first('.js-user-search').click + click_link 'Unassigned' click_button 'Save changes' - page.within '.issuable-sidebar' do - expect(page).to have_content date.to_s(:medium) + page.within('.assignee') do + expect(page).to have_content 'None - assign yourself' end end - it 'warns about version conflict' do - issue.update(title: "New title") + context 'with due date' do + before do + visit edit_project_issue_path(project, issue) + end - fill_in 'issue_title', with: 'bug 345' - fill_in 'issue_description', with: 'bug description' + it 'saves with due date' do + date = Date.today.at_beginning_of_month.tomorrow - click_button 'Save changes' + fill_in 'issue_title', with: 'bug 345' + fill_in 'issue_description', with: 'bug description' + find('#issuable-due-date').click - expect(page).to have_content 'Someone edited the issue the same time you did' - end - end - end + page.within '.pika-single' do + click_button date.day + end - context "from issue#show" do - before do - visit project_issue_path(project, issue) - end + expect(find('#issuable-due-date').value).to eq date.to_s + + click_button 'Save changes' + + page.within '.issuable-sidebar' do + expect(page).to have_content date.to_s(:medium) + end + end - describe 'update labels' do - it 'will not send ajax request when no data is changed' do - page.within '.labels' do - click_on 'Edit' + it 'warns about version conflict' do + issue.update(title: "New title") - find('.dropdown-title button').click + fill_in 'issue_title', with: 'bug 345' + fill_in 'issue_description', with: 'bug description' - expect(page).not_to have_selector('.block-loading') - expect(page).not_to have_selector('.gl-spinner') + click_button 'Save changes' + + expect(page).to have_content 'Someone edited the issue the same time you did' end end end - describe 'update assignee' do - context 'by authorized user' do - def close_dropdown_menu_if_visible - find('.dropdown-menu-toggle', visible: :all).tap do |toggle| - toggle.click if toggle.visible? + context "from issue#show" do + before do + visit project_issue_path(project, issue) + end + + describe 'update labels' do + it 'will not send ajax request when no data is changed' do + page.within '.labels' do + click_on 'Edit' + + find('.dropdown-title button').click + + expect(page).not_to have_selector('.block-loading') + expect(page).not_to have_selector('.gl-spinner') end end - it 'allows user to select unassigned' do - visit project_issue_path(project, issue) + it 'can add label to issue' do + page.within '.block.labels' do + expect(page).to have_text('verisimilitude') + expect(page).not_to have_text('syzygy') - page.within('.assignee') do - expect(page).to have_content "#{user.name}" + click_on 'Edit' - click_link 'Edit' - click_link 'Unassigned' - first('.title').click - expect(page).to have_content 'None - assign yourself' + wait_for_requests + + click_on 'syzygy' + find('.dropdown-header-button').click + + wait_for_requests + + expect(page).to have_text('verisimilitude') + expect(page).to have_text('syzygy') end end - it 'allows user to select an assignee' do - issue2 = create(:issue, project: project, author: user) - visit project_issue_path(project, issue2) + it 'can remove label from issue by clicking on the label `x` button' do + page.within '.block.labels' do + expect(page).to have_text('verisimilitude') + + within '.gl-label' do + click_button + end + + wait_for_requests - page.within('.assignee') do - expect(page).to have_content "None" + expect(page).not_to have_text('verisimilitude') end + end + end - page.within '.assignee' do - click_link 'Edit' + describe 'update assignee' do + context 'by authorized user' do + def close_dropdown_menu_if_visible + find('.dropdown-menu-toggle', visible: :all).tap do |toggle| + toggle.click if toggle.visible? + end end - page.within '.dropdown-menu-user' do - click_link user.name + it 'allows user to select unassigned' do + visit project_issue_path(project, issue) + + page.within('.assignee') do + expect(page).to have_content "#{user.name}" + + click_link 'Edit' + click_link 'Unassigned' + first('.title').click + expect(page).to have_content 'None - assign yourself' + end end - page.within('.assignee') do - expect(page).to have_content user.name + it 'allows user to select an assignee' do + issue2 = create(:issue, project: project, author: user) + visit project_issue_path(project, issue2) + + page.within('.assignee') do + expect(page).to have_content "None" + end + + page.within '.assignee' do + click_link 'Edit' + end + + page.within '.dropdown-menu-user' do + click_link user.name + end + + page.within('.assignee') do + expect(page).to have_content user.name + end end - end - it 'allows user to unselect themselves' do - issue2 = create(:issue, project: project, author: user, assignees: [user]) + it 'allows user to unselect themselves' do + issue2 = create(:issue, project: project, author: user, assignees: [user]) - visit project_issue_path(project, issue2) + visit project_issue_path(project, issue2) - page.within '.assignee' do - expect(page).to have_content user.name + page.within '.assignee' do + expect(page).to have_content user.name - click_link 'Edit' - click_link user.name + click_link 'Edit' + click_link user.name - close_dropdown_menu_if_visible + close_dropdown_menu_if_visible - page.within '.value .assign-yourself' do - expect(page).to have_content "None" + page.within '.value .assign-yourself' do + expect(page).to have_content "None" + end end end end - end - context 'by unauthorized user' do - let(:guest) { create(:user) } + context 'by unauthorized user' do + let(:guest) { create(:user) } - before do - project.add_guest(guest) - end + before do + project.add_guest(guest) + end - it 'shows assignee text' do - sign_out(:user) - sign_in(guest) + it 'shows assignee text' do + sign_out(:user) + sign_in(guest) - visit project_issue_path(project, issue) - expect(page).to have_content issue.assignees.first.name + visit project_issue_path(project, issue) + expect(page).to have_content issue.assignees.first.name + end end end - end - describe 'update milestone' do - context 'by authorized user' do - it 'allows user to select unassigned' do - visit project_issue_path(project, issue) + describe 'update milestone' do + context 'by authorized user' do + it 'allows user to select unassigned' do + visit project_issue_path(project, issue) - page.within('.milestone') do - expect(page).to have_content "None" - end + page.within('.milestone') do + expect(page).to have_content "None" + end - find('.block.milestone .edit-link').click - sleep 2 # wait for ajax stuff to complete - first('.dropdown-content li').click - sleep 2 - page.within('.milestone') do - expect(page).to have_content 'None' + find('.block.milestone .edit-link').click + sleep 2 # wait for ajax stuff to complete + first('.dropdown-content li').click + sleep 2 + page.within('.milestone') do + expect(page).to have_content 'None' + end end - end - it 'allows user to de-select milestone' do - visit project_issue_path(project, issue) + it 'allows user to de-select milestone' do + visit project_issue_path(project, issue) - page.within('.milestone') do - click_link 'Edit' - click_link milestone.title + page.within('.milestone') do + click_link 'Edit' + click_link milestone.title - page.within '.value' do - expect(page).to have_content milestone.title - end + page.within '.value' do + expect(page).to have_content milestone.title + end - click_link 'Edit' - click_link milestone.title + click_link 'Edit' + click_link milestone.title - page.within '.value' do - expect(page).to have_content 'None' + page.within '.value' do + expect(page).to have_content 'None' + end end end - end - it 'allows user to search milestone' do - visit project_issue_path(project_with_milestones, issue_with_milestones) + it 'allows user to search milestone' do + visit project_issue_path(project_with_milestones, issue_with_milestones) - page.within('.milestone') do - click_link 'Edit' - wait_for_requests - # We need to enclose search string in quotes for exact match as all the milestone titles - # within tests are prefixed with `My title`. - find('.dropdown-input-field', visible: true).send_keys "\"#{milestones[0].title}\"" - wait_for_requests + page.within('.milestone') do + click_link 'Edit' + wait_for_requests + # We need to enclose search string in quotes for exact match as all the milestone titles + # within tests are prefixed with `My title`. + find('.dropdown-input-field', visible: true).send_keys "\"#{milestones[0].title}\"" + wait_for_requests - page.within '.dropdown-content' do - expect(page).to have_content milestones[0].title + page.within '.dropdown-content' do + expect(page).to have_content milestones[0].title + end end end end - end - context 'by unauthorized user' do - let(:guest) { create(:user) } + context 'by unauthorized user' do + let(:guest) { create(:user) } - before do - project.add_guest(guest) - issue.milestone = milestone - issue.save - end + before do + project.add_guest(guest) + issue.milestone = milestone + issue.save + end - it 'shows milestone text' do - sign_out(:user) - sign_in(guest) + it 'shows milestone text' do + sign_out(:user) + sign_in(guest) - visit project_issue_path(project, issue) - expect(page).to have_content milestone.title + visit project_issue_path(project, issue) + expect(page).to have_content milestone.title + end end end - end - context 'update due date' do - it 'adds due date to issue' do - date = Date.today.at_beginning_of_month + 2.days + context 'update due date' do + it 'adds due date to issue' do + date = Date.today.at_beginning_of_month + 2.days - page.within '.due_date' do - click_link 'Edit' + page.within '.due_date' do + click_link 'Edit' - page.within '.pika-single' do - click_button date.day - end + page.within '.pika-single' do + click_button date.day + end - wait_for_requests + wait_for_requests - expect(find('.value').text).to have_content date.strftime('%b %-d, %Y') + expect(find('.value').text).to have_content date.strftime('%b %-d, %Y') + end end - end - it 'removes due date from issue' do - date = Date.today.at_beginning_of_month + 2.days + it 'removes due date from issue' do + date = Date.today.at_beginning_of_month + 2.days - page.within '.due_date' do - click_link 'Edit' + page.within '.due_date' do + click_link 'Edit' - page.within '.pika-single' do - click_button date.day + page.within '.pika-single' do + click_button date.day + end + + wait_for_requests + + expect(page).to have_no_content 'None' + + click_link 'remove due date' + expect(page).to have_content 'None' end + end + end + end + end - wait_for_requests + context 'with unauthorized user' do + before do + sign_in(user) + end - expect(page).to have_no_content 'None' + context "from issue#show" do + before do + visit project_issue_path(project, issue) + end - click_link 'remove due date' - expect(page).to have_content 'None' + describe 'updating labels' do + it 'cannot edit labels' do + page.within '.block.labels' do + expect(page).not_to have_button('Edit') + end + end + + it 'cannot remove label with a click as it has no `x` button' do + page.within '.block.labels' do + within '.gl-label' do + expect(page).not_to have_button + end + end end end end diff --git a/spec/features/issues/user_sees_live_update_spec.rb b/spec/features/issues/user_sees_live_update_spec.rb index c9b751715bc..d27cdb774a5 100644 --- a/spec/features/issues/user_sees_live_update_spec.rb +++ b/spec/features/issues/user_sees_live_update_spec.rb @@ -39,7 +39,7 @@ RSpec.describe 'Issues > User sees live update', :js do expect(page).to have_css('.sidebar-item-warning-message') within('.sidebar-item-warning-message') do - find('.btn-close').click + find('[data-testid="confidential-toggle"]').click end wait_for_requests diff --git a/spec/features/issues/user_views_issue_spec.rb b/spec/features/issues/user_views_issue_spec.rb index 3f18764aa58..9b1c8be1513 100644 --- a/spec/features/issues/user_views_issue_spec.rb +++ b/spec/features/issues/user_views_issue_spec.rb @@ -5,7 +5,7 @@ require "spec_helper" RSpec.describe "User views issue" do let_it_be(:project) { create(:project_empty_repo, :public) } let_it_be(:user) { create(:user) } - let_it_be(:issue) { create(:issue, project: project, description: "# Description header", author: user) } + let_it_be(:issue) { create(:issue, project: project, description: "# Description header\n\n**Lorem** _ipsum_ dolor sit [amet](https://example.com)", author: user) } let_it_be(:note) { create(:note, noteable: issue, project: project, author: user) } before_all do @@ -20,6 +20,8 @@ RSpec.describe "User views issue" do it { expect(page).to have_header_with_correct_id_and_link(1, "Description header", "description-header") } + it_behaves_like 'page meta description', ' Description header Lorem ipsum dolor sit amet' + it 'shows the merge request and issue actions', :aggregate_failures do expect(page).to have_link('New issue') expect(page).to have_button('Create merge request') diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb index eed9a6d1043..5d141580874 100644 --- a/spec/features/labels_hierarchy_spec.rb +++ b/spec/features/labels_hierarchy_spec.rb @@ -17,6 +17,7 @@ RSpec.describe 'Labels Hierarchy', :js do let!(:project_label_1) { create(:label, project: project_1, title: 'Label_4') } before do + stub_feature_flags(graphql_board_lists: false) grandparent.add_owner(user) sign_in(user) diff --git a/spec/features/merge_request/batch_comments_spec.rb b/spec/features/merge_request/batch_comments_spec.rb index 40f6482c948..c8fc23bebf9 100644 --- a/spec/features/merge_request/batch_comments_spec.rb +++ b/spec/features/merge_request/batch_comments_spec.rb @@ -41,7 +41,6 @@ RSpec.describe 'Merge request > Batch comments', :js do write_comment page.within('.review-bar-content') do - click_button 'Finish review' click_button 'Submit review' end @@ -64,18 +63,6 @@ RSpec.describe 'Merge request > Batch comments', :js do expect(page).to have_selector('.note:not(.draft-note)', text: 'Line is wrong') end - it 'discards review' do - write_comment - - click_button 'Discard review' - - click_button 'Delete all pending comments' - - wait_for_requests - - expect(page).not_to have_selector('.draft-note-component') - end - it 'deletes draft note' do write_comment @@ -149,7 +136,6 @@ RSpec.describe 'Merge request > Batch comments', :js do write_reply_to_discussion(resolve: true) page.within('.review-bar-content') do - click_button 'Finish review' click_button 'Submit review' end @@ -192,7 +178,6 @@ RSpec.describe 'Merge request > Batch comments', :js do write_reply_to_discussion(button_text: 'Start a review', unresolve: true) page.within('.review-bar-content') do - click_button 'Finish review' click_button 'Submit review' end diff --git a/spec/features/merge_request/maintainer_edits_fork_spec.rb b/spec/features/merge_request/maintainer_edits_fork_spec.rb index 0e65cb358da..a98bfd1c8a4 100644 --- a/spec/features/merge_request/maintainer_edits_fork_spec.rb +++ b/spec/features/merge_request/maintainer_edits_fork_spec.rb @@ -26,7 +26,12 @@ RSpec.describe 'a maintainer edits files on a source-branch of an MR from a fork visit project_merge_request_path(target_project, merge_request) click_link 'Changes' wait_for_requests - first('.js-file-title').find('.js-edit-blob').click + + page.within(first('.js-file-title')) do + find('.js-diff-more-actions').click + find('.js-edit-blob').click + end + wait_for_requests end diff --git a/spec/features/merge_request/user_comments_on_diff_spec.rb b/spec/features/merge_request/user_comments_on_diff_spec.rb index 3a199951b56..ad1ad067935 100644 --- a/spec/features/merge_request/user_comments_on_diff_spec.rb +++ b/spec/features/merge_request/user_comments_on_diff_spec.rb @@ -34,7 +34,8 @@ RSpec.describe 'User comments on a diff', :js do page.within('.diff-files-holder > div:nth-child(3)') do expect(page).to have_content('Line is wrong') - find('.js-btn-vue-toggle-comments').click + find('.js-diff-more-actions').click + click_button 'Hide comments on this file' expect(page).not_to have_content('Line is wrong') end @@ -67,7 +68,8 @@ RSpec.describe 'User comments on a diff', :js do # Hide the comment. page.within('.diff-files-holder > div:nth-child(3)') do - find('.js-btn-vue-toggle-comments').click + find('.js-diff-more-actions').click + click_button 'Hide comments on this file' expect(page).not_to have_content('Line is wrong') end @@ -80,7 +82,8 @@ RSpec.describe 'User comments on a diff', :js do # Show the comment. page.within('.diff-files-holder > div:nth-child(3)') do - find('.js-btn-vue-toggle-comments').click + find('.js-diff-more-actions').click + click_button 'Show comments on this file' end # Now both the comments should be shown. diff --git a/spec/features/merge_request/user_edits_assignees_sidebar_spec.rb b/spec/features/merge_request/user_edits_assignees_sidebar_spec.rb index affd6f6b7b5..7d55a72c2b1 100644 --- a/spec/features/merge_request/user_edits_assignees_sidebar_spec.rb +++ b/spec/features/merge_request/user_edits_assignees_sidebar_spec.rb @@ -20,7 +20,7 @@ RSpec.describe 'Merge request > User edits assignees sidebar', :js do let(:sidebar_assignee_dropdown_item) { sidebar_assignee_block.find(".dropdown-menu li[data-user-id=\"#{assignee.id}\"]") } let(:sidebar_assignee_dropdown_tooltip) { sidebar_assignee_dropdown_item.find('a')['data-title'] || '' } - context 'when invite_members_version_a experiment is not enabled' do + context 'when user is an owner' do before do stub_const('Autocomplete::UsersFinder::LIMIT', users_find_limit) @@ -52,12 +52,6 @@ RSpec.describe 'Merge request > User edits assignees sidebar', :js do it "shows assignee tooltip '#{expected_tooltip}" do expect(sidebar_assignee_dropdown_tooltip).to eql(expected_tooltip) end - - it 'does not show invite link' do - page.within '.dropdown-menu-user' do - expect(page).not_to have_link('Invite Members') - end - end end end @@ -74,48 +68,15 @@ RSpec.describe 'Merge request > User edits assignees sidebar', :js do end end - context 'when invite_members_version_a experiment is enabled' do + context 'with invite members experiment considerations' do let_it_be(:user) { create(:user) } before do - stub_experiment_for_user(invite_members_version_a: true) sign_in(user) end - context 'when user can not see invite members' do - before do - project.add_developer(user) - visit project_merge_request_path(project, merge_request) - - find('.block.assignee .edit-link').click - - wait_for_requests - end - - it 'does not see link to invite members' do - page.within '.dropdown-menu-user' do - expect(page).not_to have_link('Invite Members') - end - end - end - - context 'when user can see invite members' do - before do - project.add_maintainer(user) - visit project_merge_request_path(project, merge_request) - - find('.block.assignee .edit-link').click - - wait_for_requests - end - - it 'sees link to invite members' do - page.within '.dropdown-menu-user' do - expect(page).to have_link('Invite Members', href: project_project_members_path(project)) - expect(page).to have_selector('[data-track-event="click_invite_members"]') - expect(page).to have_selector("[data-track-label='edit_assignee']") - end - end + include_examples 'issuable invite members experiments' do + let(:issuable_path) { project_merge_request_path(project, merge_request) } end end end diff --git a/spec/features/merge_request/user_edits_mr_spec.rb b/spec/features/merge_request/user_edits_mr_spec.rb index 397ca70f4a1..817b4e0b48e 100644 --- a/spec/features/merge_request/user_edits_mr_spec.rb +++ b/spec/features/merge_request/user_edits_mr_spec.rb @@ -21,24 +21,6 @@ RSpec.describe 'Merge request > User edits MR' do it_behaves_like 'an editable merge request' end - context 'when merge_request_reviewers is turned on' do - before do - stub_feature_flags(merge_request_reviewers: true) - end - - context 'non-fork merge request' do - include_context 'merge request edit context' - it_behaves_like 'an editable merge request with reviewers' - end - - context 'for a forked project' do - let(:source_project) { fork_project(target_project, nil, repository: true) } - - include_context 'merge request edit context' - it_behaves_like 'an editable merge request with reviewers' - end - end - context 'when merge_request_reviewers is turned off' do before do stub_feature_flags(merge_request_reviewers: false) diff --git a/spec/features/merge_request/user_expands_diff_spec.rb b/spec/features/merge_request/user_expands_diff_spec.rb index 0340d9ccc3d..0cdc87de761 100644 --- a/spec/features/merge_request/user_expands_diff_spec.rb +++ b/spec/features/merge_request/user_expands_diff_spec.rb @@ -7,6 +7,7 @@ RSpec.describe 'User expands diff', :js do let(:merge_request) { create(:merge_request, source_branch: 'expand-collapse-files', source_project: project, target_project: project) } before do + stub_feature_flags(increased_diff_limits: false) visit(diffs_project_merge_request_path(project, merge_request)) wait_for_requests diff --git a/spec/features/merge_request/user_marks_merge_request_as_draft_spec.rb b/spec/features/merge_request/user_marks_merge_request_as_draft_spec.rb new file mode 100644 index 00000000000..f5bca7cf015 --- /dev/null +++ b/spec/features/merge_request/user_marks_merge_request_as_draft_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Merge request > User marks merge request as draft', :js do + let(:user) { create(:user) } + let(:project) { create(:project, :public, :repository) } + let(:merge_request) { create(:merge_request, source_project: project) } + + before do + project.add_maintainer(user) + + sign_in(user) + + visit project_merge_request_path(project, merge_request) + end + + it 'toggles draft status' do + click_link 'Mark as draft' + + expect(page).to have_content("Draft: #{merge_request.title}") + + page.within('.detail-page-header-actions') do + click_link 'Mark as ready' + end + + expect(page).to have_content(merge_request.title) + end +end diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb index 3dc49fb4dea..444d5371e7a 100644 --- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb +++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb @@ -47,7 +47,7 @@ RSpec.describe 'Merge request > User merges when pipeline succeeds', :js do it_behaves_like 'Merge when pipeline succeeds activator' end - context 'when enabled after pipeline status changed' do + context 'when enabled after pipeline status changed', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/258667' do before do pipeline.run! diff --git a/spec/features/merge_request/user_reopens_merge_request_spec.rb b/spec/features/merge_request/user_reopens_merge_request_spec.rb index 7866ece84ac..4a05a3be59a 100644 --- a/spec/features/merge_request/user_reopens_merge_request_spec.rb +++ b/spec/features/merge_request/user_reopens_merge_request_spec.rb @@ -15,7 +15,11 @@ RSpec.describe 'User reopens a merge requests', :js do end it 'reopens a merge request' do - click_button('Reopen merge request', match: :first) + find('.js-issuable-close-dropdown .dropdown-toggle').click + + click_link('Reopen merge request', match: :first) + + wait_for_requests page.within('.status-box') do expect(page).to have_content('Open') diff --git a/spec/features/merge_request/user_resolves_wip_mr_spec.rb b/spec/features/merge_request/user_resolves_wip_mr_spec.rb index a9d4c4df507..b67167252e1 100644 --- a/spec/features/merge_request/user_resolves_wip_mr_spec.rb +++ b/spec/features/merge_request/user_resolves_wip_mr_spec.rb @@ -35,7 +35,9 @@ RSpec.describe 'Merge request > User resolves Work in Progress', :js do expect(page.find('.ci-widget-content')).to have_content("Pipeline ##{pipeline.id}") expect(page).to have_content "This merge request is still a work in progress." - click_button('Mark as ready') + page.within('.mr-state-widget') do + click_button('Mark as ready') + end wait_for_requests diff --git a/spec/features/merge_request/user_sees_diff_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb index 7a3a14e61e3..a7713ed9964 100644 --- a/spec/features/merge_request/user_sees_diff_spec.rb +++ b/spec/features/merge_request/user_sees_diff_spec.rb @@ -63,7 +63,7 @@ RSpec.describe 'Merge request > User sees diff', :js do visit diffs_project_merge_request_path(project, merge_request) # Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax - expect(page).to have_selector("[id=\"#{changelog_id}\"] a.js-edit-blob") + expect(page).to have_selector("[id=\"#{changelog_id}\"] .js-edit-blob", visible: false) end end @@ -73,6 +73,7 @@ RSpec.describe 'Merge request > User sees diff', :js do visit diffs_project_merge_request_path(project, merge_request) # Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax + find("[id=\"#{changelog_id}\"] .js-diff-more-actions").click find("[id=\"#{changelog_id}\"] .js-edit-blob").click expect(page).to have_selector('.js-fork-suggestion-button', count: 1) diff --git a/spec/features/merge_request/user_sees_page_metadata_spec.rb b/spec/features/merge_request/user_sees_page_metadata_spec.rb new file mode 100644 index 00000000000..7b3e07152a0 --- /dev/null +++ b/spec/features/merge_request/user_sees_page_metadata_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Merge request > User sees page metadata' do + let(:merge_request) { create(:merge_request, description: '**Lorem** _ipsum_ dolor sit [amet](https://example.com)') } + let(:project) { merge_request.target_project } + let(:user) { project.creator } + + before do + project.add_maintainer(user) + sign_in(user) + visit project_merge_request_path(project, merge_request) + end + + it_behaves_like 'page meta description', 'Lorem ipsum dolor sit amet' +end diff --git a/spec/features/merge_request/user_sees_pipelines_spec.rb b/spec/features/merge_request/user_sees_pipelines_spec.rb index 8e15ba6cf8d..107fc002ebd 100644 --- a/spec/features/merge_request/user_sees_pipelines_spec.rb +++ b/spec/features/merge_request/user_sees_pipelines_spec.rb @@ -50,7 +50,7 @@ RSpec.describe 'Merge request > User sees pipelines', :js do wait_for_requests - expect(page.find('.js-run-mr-pipeline')).to have_text('Run Pipeline') + expect(page.find('[data-testid="run_pipeline_button"]')).to have_text('Run Pipeline') end end @@ -66,7 +66,7 @@ RSpec.describe 'Merge request > User sees pipelines', :js do wait_for_requests - expect(page.find('.js-run-mr-pipeline')).to have_text('Run Pipeline') + expect(page.find('[data-testid="run_pipeline_button"]')).to have_text('Run Pipeline') end end end diff --git a/spec/features/merge_request/user_sees_suggest_pipeline_spec.rb b/spec/features/merge_request/user_sees_suggest_pipeline_spec.rb new file mode 100644 index 00000000000..93807512d9c --- /dev/null +++ b/spec/features/merge_request/user_sees_suggest_pipeline_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Merge request > User sees suggest pipeline', :js do + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.source_project } + let(:user) { project.creator } + + before do + stub_application_setting(auto_devops_enabled: false) + stub_experiment(suggest_pipeline: true) + stub_experiment_for_user(suggest_pipeline: true) + project.add_maintainer(user) + sign_in(user) + visit project_merge_request_path(project, merge_request) + end + + it 'shows the suggest pipeline widget and then allows dismissal correctly' do + expect(page).to have_content('Are you adding technical debt or code vulnerabilities?') + + page.within '.mr-pipeline-suggest' do + find('[data-testid="close"]').click + end + + wait_for_requests + + expect(page).not_to have_content('Are you adding technical debt or code vulnerabilities?') + + # Reload so we know the user callout was registered + visit page.current_url + + expect(page).not_to have_content('Are you adding technical debt or code vulnerabilities?') + end +end diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb index 39495832547..9268190c7e0 100644 --- a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb +++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb @@ -119,7 +119,8 @@ RSpec.describe 'User comments on a diff', :js do it 'can add and remove suggestions from a batch' do files.each_with_index do |file, index| page.within("[id='#{file[:hash]}']") do - find("button[title='Show full file']").click + find('.js-diff-more-actions').click + click_button 'Show full file' wait_for_requests click_diff_line(find("[id='#{file[:line_code]}']")) @@ -130,7 +131,9 @@ RSpec.describe 'User comments on a diff', :js do wait_for_requests end end + end + files.each_with_index do |file, index| page.within("[id='#{file[:hash]}']") do expect(page).not_to have_content('Applied') @@ -247,7 +250,7 @@ RSpec.describe 'User comments on a diff', :js do end context 'multiple suggestions in a single note' do - it 'suggestions are presented' do + it 'suggestions are presented', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/258989' do click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) page.within('.js-discussion-note-form') do diff --git a/spec/features/merge_request/user_views_open_merge_request_spec.rb b/spec/features/merge_request/user_views_open_merge_request_spec.rb index 448844ae57d..e8998f9457a 100644 --- a/spec/features/merge_request/user_views_open_merge_request_spec.rb +++ b/spec/features/merge_request/user_views_open_merge_request_spec.rb @@ -22,7 +22,24 @@ RSpec.describe 'User views an open merge request' do # returns the whole document, not the node's actual parent element expect(find(:xpath, "#{node.path}/..").text).to eq(merge_request.description[2..-1]) - expect(page).to have_content(merge_request.title).and have_content(merge_request.description) + expect(page).to have_content(merge_request.title) + end + + it 'has reviewers in sidebar' do + expect(page).to have_css('.reviewer') + end + end + + context 'when merge_request_reviewers is turned off' do + let(:project) { create(:project, :public, :repository) } + + before do + stub_feature_flags(merge_request_reviewers: false) + visit(merge_request_path(merge_request)) + end + + it 'has reviewers in sidebar' do + expect(page).not_to have_css('.reviewer') end end diff --git a/spec/features/merge_requests/user_filters_by_approvals_spec.rb b/spec/features/merge_requests/user_filters_by_approvals_spec.rb new file mode 100644 index 00000000000..6dda9ca7952 --- /dev/null +++ b/spec/features/merge_requests/user_filters_by_approvals_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Merge Requests > User filters', :js do + include FilteredSearchHelpers + + let_it_be(:project) { create(:project, :public, :repository) } + let_it_be(:user) { project.creator } + let_it_be(:group_user) { create(:user) } + let_it_be(:first_user) { create(:user) } + + before do + sign_in(user) + visit project_merge_requests_path(project) + end + + context 'by "approved by"' do + let_it_be(:merge_request) { create(:merge_request, title: 'Bugfix3', source_project: project, source_branch: 'bugfix3') } + + let_it_be(:merge_request_with_first_user_approval) do + create(:merge_request, source_project: project, title: 'Bugfix5').tap do |mr| + create(:approval, merge_request: mr, user: first_user) + end + end + + let_it_be(:merge_request_with_group_user_approved) do + group = create(:group) + group.add_developer(group_user) + + create(:merge_request, source_project: project, title: 'Bugfix6', source_branch: 'bugfix6').tap do |mr| + create(:approval, merge_request: mr, user: group_user) + end + end + + context 'filtering by approved-by:none' do + it 'applies the filter' do + input_filtered_search('approved-by:=none') + + expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) + + expect(page).not_to have_content 'Bugfix5' + expect(page).not_to have_content 'Bugfix6' + expect(page).to have_content 'Bugfix3' + end + end + + context 'filtering by approved-by:any' do + it 'applies the filter' do + input_filtered_search('approved-by:=any') + + expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) + + expect(page).to have_content 'Bugfix5' + expect(page).not_to have_content 'Bugfix3' + end + end + + context 'filtering by approved-by:@username' do + it 'applies the filter' do + input_filtered_search("approved-by:=@#{first_user.username}") + + expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) + + expect(page).to have_content 'Bugfix5' + expect(page).not_to have_content 'Bugfix3' + end + end + + context 'filtering by an approver from a group' do + it 'applies the filter' do + input_filtered_search("approved-by:=@#{group_user.username}") + + expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) + + expect(page).to have_content 'Bugfix6' + expect(page).not_to have_content 'Bugfix5' + expect(page).not_to have_content 'Bugfix3' + end + end + end +end diff --git a/spec/features/merge_requests/user_filters_by_deployments_spec.rb b/spec/features/merge_requests/user_filters_by_deployments_spec.rb new file mode 100644 index 00000000000..157454d4e10 --- /dev/null +++ b/spec/features/merge_requests/user_filters_by_deployments_spec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Merge Requests > User filters by deployments', :js do + include FilteredSearchHelpers + + let!(:project) { create(:project, :public, :repository) } + let!(:user) { project.creator } + let!(:gstg) { create(:environment, project: project, name: 'gstg') } + let!(:gprd) { create(:environment, project: project, name: 'gprd') } + + let(:mr1) do + create( + :merge_request, + :simple, + :merged, + author: user, + source_project: project, + target_project: project + ) + end + + let(:mr2) do + create( + :merge_request, + :simple, + :merged, + author: user, + source_project: project, + target_project: project + ) + end + + let(:deploy1) do + create( + :deployment, + :success, + deployable: nil, + environment: gstg, + project: project, + sha: mr1.diff_head_sha, + finished_at: Time.utc(2020, 10, 1, 0, 0) + ) + end + + let(:deploy2) do + create( + :deployment, + :success, + deployable: nil, + environment: gprd, + project: project, + sha: mr2.diff_head_sha, + finished_at: Time.utc(2020, 10, 2, 0, 0) + ) + end + + before do + deploy1.link_merge_requests(MergeRequest.where(id: mr1.id)) + deploy2.link_merge_requests(MergeRequest.where(id: mr2.id)) + + sign_in(user) + visit(project_merge_requests_path(project, state: :merged)) + end + + describe 'filtering by deployed-before' do + it 'applies the filter' do + input_filtered_search('deployed-before:=2020-10-02') + + expect(page).to have_issuable_counts(open: 0, merged: 1, all: 1) + expect(page).to have_content mr1.title + end + end + + describe 'filtering by deployed-after' do + it 'applies the filter' do + input_filtered_search('deployed-after:=2020-10-01') + + expect(page).to have_issuable_counts(open: 0, merged: 1, all: 1) + expect(page).to have_content mr2.title + end + end + + describe 'filtering by environment' do + it 'applies the filter' do + input_filtered_search('environment:=gstg') + + expect(page).to have_issuable_counts(open: 0, merged: 1, all: 1) + expect(page).to have_content mr1.title + end + end +end diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb index 4531ef40901..36d28ae2822 100644 --- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb +++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb @@ -8,6 +8,10 @@ RSpec.describe 'Merge requests > User lists merge requests' do let(:project) { create(:project, :public, :repository) } let(:user) { create(:user) } + let(:user2) { create(:user) } + let(:user3) { create(:user) } + let(:user4) { create(:user) } + let(:user5) { create(:user) } before do @fix = create(:merge_request, @@ -15,6 +19,7 @@ RSpec.describe 'Merge requests > User lists merge requests' do source_project: project, source_branch: 'fix', assignees: [user], + reviewers: [user, user2, user3, user4, user5], milestone: create(:milestone, project: project, due_date: '2013-12-11'), created_at: 1.minute.ago, updated_at: 1.minute.ago) @@ -23,6 +28,7 @@ RSpec.describe 'Merge requests > User lists merge requests' do source_project: project, source_branch: 'markdown', assignees: [user], + reviewers: [user, user2, user3, user4], milestone: create(:milestone, project: project, due_date: '2013-12-12'), created_at: 2.minutes.ago, updated_at: 2.minutes.ago) @@ -34,6 +40,37 @@ RSpec.describe 'Merge requests > User lists merge requests' do updated_at: 10.seconds.ago) end + context 'when merge_request_reviewers is turned on' do + before do + stub_feature_flags(merge_request_reviewers: true) + visit_merge_requests(project, reviewer_id: user.id) + end + + it 'has reviewers in MR list' do + expect(page).to have_css('.issuable-reviewers') + end + + it 'shows reviewers avatar count badge if more_reviewers_count > 4' do + first_issuable_reviewers = first('.issuable-reviewers') + + expect(first_issuable_reviewers).to have_content('2') + expect(first_issuable_reviewers).to have_css('.avatar-counter') + end + + it 'does not show reviewers avatar count badge if more_reviewers_count <= 4' do + expect(page.all('.issuable-reviewers')[1]).not_to have_css('.avatar-counter') + end + end + + context 'when merge_request_reviewers is turned false' do + it 'has no reviewers in MR list' do + stub_feature_flags(merge_request_reviewers: false) + visit_merge_requests(project, reviewer_id: user.id) + + expect(page).not_to have_css('.issuable-reviewers') + end + end + it 'filters on no assignee' do visit_merge_requests(project, assignee_id: IssuableFinder::Params::FILTER_NONE) diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb index 4a7f14d5a1b..fefa2916c30 100644 --- a/spec/features/milestone_spec.rb +++ b/spec/features/milestone_spec.rb @@ -25,7 +25,7 @@ RSpec.describe 'Milestone' do find('input[name="commit"]').click - expect(find('.alert-success')).to have_content('Assign some issues to this milestone.') + expect(find('[data-testid="no-issues-alert"]')).to have_content('Assign some issues to this milestone.') expect(page).to have_content('Nov 16, 2016–Dec 16, 2016') end end @@ -37,7 +37,7 @@ RSpec.describe 'Milestone' do create(:issue, title: "Bugfix1", project: project, milestone: milestone, state: "closed") visit project_milestone_path(project, milestone) - expect(find('.alert-success')).to have_content('All issues for this milestone are closed. You may close this milestone now.') + expect(find('[data-testid="all-issues-closed-alert"]')).to have_content('All issues for this milestone are closed. You may close this milestone now.') end end diff --git a/spec/features/milestones/user_views_milestone_spec.rb b/spec/features/milestones/user_views_milestone_spec.rb index 420f8d49483..9c19f842427 100644 --- a/spec/features/milestones/user_views_milestone_spec.rb +++ b/spec/features/milestones/user_views_milestone_spec.rb @@ -4,15 +4,27 @@ require 'spec_helper' RSpec.describe "User views milestone" do let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project) } - let_it_be(:milestone) { create(:milestone, project: project) } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :repository, group: group) } + let_it_be(:milestone) { create(:milestone, project: project, description: '**Lorem** _ipsum_ dolor sit [amet](https://example.com)') } let_it_be(:labels) { create_list(:label, 2, project: project) } - before do + before_all do project.add_developer(user) + end + + before do sign_in(user) end + context 'page description' do + before do + visit(project_milestone_path(project, milestone)) + end + + it_behaves_like 'page meta description', 'Lorem ipsum dolor sit amet' + end + it "avoids N+1 database queries" do issue_params = { project: project, assignees: [user], author: user, milestone: milestone, labels: labels }.freeze @@ -25,7 +37,7 @@ RSpec.describe "User views milestone" do expect { visit_milestone }.not_to exceed_query_limit(control) end - context 'limiting milestone issues' do + context 'issues list', :js do before_all do 2.times do create(:issue, milestone: milestone, project: project) @@ -34,6 +46,28 @@ RSpec.describe "User views milestone" do end end + context 'for a project milestone' do + it 'does not show the project name' do + visit(project_milestone_path(project, milestone)) + + wait_for_requests + + expect(page.find('#tab-issues')).not_to have_text(project.name) + end + end + + context 'for a group milestone' do + let(:group_milestone) { create(:milestone, group: group) } + + it 'shows the project name' do + create(:issue, project: project, milestone: group_milestone) + + visit(group_milestone_path(group, group_milestone)) + + expect(page.find('#tab-issues')).to have_text(project.name) + end + end + context 'when issues on milestone are over DISPLAY_ISSUES_LIMIT' do it "limits issues to display and shows warning" do stub_const('Milestoneish::DISPLAY_ISSUES_LIMIT', 3) @@ -56,6 +90,40 @@ RSpec.describe "User views milestone" do end end + context 'merge requests list', :js do + context 'for a project milestone' do + it 'does not show the project name' do + create(:merge_request, source_project: project, milestone: milestone) + + visit(project_milestone_path(project, milestone)) + + within('.js-milestone-tabs') do + click_link('Merge Requests') + end + + wait_for_requests + + expect(page.find('#tab-merge-requests')).not_to have_text(project.name) + end + end + + context 'for a group milestone' do + let(:group_milestone) { create(:milestone, group: group) } + + it 'shows the project name' do + create(:merge_request, source_project: project, milestone: group_milestone) + + visit(group_milestone_path(group, group_milestone)) + + within('.js-milestone-tabs') do + click_link('Merge Requests') + end + + expect(page.find('#tab-merge-requests')).to have_text(project.name) + end + end + end + private def visit_milestone diff --git a/spec/features/milestones/user_views_milestones_spec.rb b/spec/features/milestones/user_views_milestones_spec.rb index 3f606577121..f8b4b802a60 100644 --- a/spec/features/milestones/user_views_milestones_spec.rb +++ b/spec/features/milestones/user_views_milestones_spec.rb @@ -21,7 +21,7 @@ RSpec.describe "User views milestones" do .and have_content("Merge Requests") end - context "with issues" do + context "with issues", :js do let_it_be(:issue) { create(:issue, project: project, milestone: milestone) } let_it_be(:closed_issue) { create(:closed_issue, project: project, milestone: milestone) } @@ -33,7 +33,6 @@ RSpec.describe "User views milestones" do .and have_selector("#tab-issues li.issuable-row", count: 2) .and have_content(issue.title) .and have_content(closed_issue.title) - .and have_selector("#tab-merge-requests") end end diff --git a/spec/features/operations_sidebar_link_spec.rb b/spec/features/operations_sidebar_link_spec.rb new file mode 100644 index 00000000000..32e2833dafb --- /dev/null +++ b/spec/features/operations_sidebar_link_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Operations dropdown sidebar' do + let_it_be(:project) { create(:project, :repository) } + let(:user) { create(:user) } + + before do + project.add_role(user, role) + sign_in(user) + visit project_issues_path(project) + end + + context 'user has guest role' do + let(:role) { :guest } + + it 'has the correct `Operations` menu items' do + expect(page).to have_link(title: 'Incidents', href: project_incidents_path(project)) + + expect(page).not_to have_link(title: 'Metrics', href: project_metrics_dashboard_path(project)) + expect(page).not_to have_link(title: 'Alerts', href: project_alert_management_index_path(project)) + expect(page).not_to have_link(title: 'Environments', href: project_environments_path(project)) + expect(page).not_to have_link(title: 'Error Tracking', href: project_error_tracking_index_path(project)) + expect(page).not_to have_link(title: 'Product Analytics', href: project_product_analytics_path(project)) + expect(page).not_to have_link(title: 'Serverless', href: project_serverless_functions_path(project)) + expect(page).not_to have_link(title: 'Logs', href: project_logs_path(project)) + expect(page).not_to have_link(title: 'Kubernetes', href: project_clusters_path(project)) + end + end + + context 'user has reporter role' do + let(:role) { :reporter } + + it 'has the correct `Operations` menu items' do + expect(page).to have_link(title: 'Metrics', href: project_metrics_dashboard_path(project)) + expect(page).to have_link(title: 'Incidents', href: project_incidents_path(project)) + expect(page).to have_link(title: 'Environments', href: project_environments_path(project)) + expect(page).to have_link(title: 'Error Tracking', href: project_error_tracking_index_path(project)) + expect(page).to have_link(title: 'Product Analytics', href: project_product_analytics_path(project)) + + expect(page).not_to have_link(title: 'Alerts', href: project_alert_management_index_path(project)) + expect(page).not_to have_link(title: 'Serverless', href: project_serverless_functions_path(project)) + expect(page).not_to have_link(title: 'Logs', href: project_logs_path(project)) + expect(page).not_to have_link(title: 'Kubernetes', href: project_clusters_path(project)) + end + end + + context 'user has developer role' do + let(:role) { :developer } + + it 'has the correct `Operations` menu items' do + expect(page).to have_link(title: 'Metrics', href: project_metrics_dashboard_path(project)) + expect(page).to have_link(title: 'Alerts', href: project_alert_management_index_path(project)) + expect(page).to have_link(title: 'Incidents', href: project_incidents_path(project)) + expect(page).to have_link(title: 'Environments', href: project_environments_path(project)) + expect(page).to have_link(title: 'Error Tracking', href: project_error_tracking_index_path(project)) + expect(page).to have_link(title: 'Product Analytics', href: project_product_analytics_path(project)) + expect(page).to have_link(title: 'Logs', href: project_logs_path(project)) + + expect(page).not_to have_link(title: 'Serverless', href: project_serverless_functions_path(project)) + expect(page).not_to have_link(title: 'Kubernetes', href: project_clusters_path(project)) + end + end + + context 'user has maintainer role' do + let(:role) { :maintainer } + + it 'has the correct `Operations` menu items' do + expect(page).to have_link(title: 'Metrics', href: project_metrics_dashboard_path(project)) + expect(page).to have_link(title: 'Alerts', href: project_alert_management_index_path(project)) + expect(page).to have_link(title: 'Incidents', href: project_incidents_path(project)) + expect(page).to have_link(title: 'Environments', href: project_environments_path(project)) + expect(page).to have_link(title: 'Error Tracking', href: project_error_tracking_index_path(project)) + expect(page).to have_link(title: 'Product Analytics', href: project_product_analytics_path(project)) + expect(page).to have_link(title: 'Serverless', href: project_serverless_functions_path(project)) + expect(page).to have_link(title: 'Logs', href: project_logs_path(project)) + expect(page).to have_link(title: 'Kubernetes', href: project_clusters_path(project)) + end + end +end diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb index b5e784a749f..23bbe9c1587 100644 --- a/spec/features/profiles/keys_spec.rb +++ b/spec/features/profiles/keys_spec.rb @@ -71,21 +71,35 @@ RSpec.describe 'Profile > SSH Keys' do expect(page).to have_content(key.title) end - it 'User removes a key via the key index' do - create(:key, user: user) - visit profile_keys_path + describe 'User removes a key', :js do + shared_examples 'removes key' do + it 'removes key' do + visit path + click_button('Delete') - click_link('Remove') + page.within('.modal') do + page.click_button('Delete') + end - expect(page).to have_content('Your SSH keys (0)') - end + expect(page).to have_content('Your SSH keys (0)') + end + end - it 'User removes a key via its details page' do - key = create(:key, user: user) - visit profile_key_path(key) + context 'via the key index' do + before do + create(:key, user: user) + end + + let(:path) { profile_keys_path } - click_link('Remove') + it_behaves_like 'removes key' + end - expect(page).to have_content('Your SSH keys (0)') + context 'via its details page' do + let(:key) { create(:key, user: user) } + let(:path) { profile_keys_path(key) } + + it_behaves_like 'removes key' + end end end diff --git a/spec/features/projects/activity/user_sees_design_comment_spec.rb b/spec/features/projects/activity/user_sees_design_comment_spec.rb index e60deba65f0..3a8e2790858 100644 --- a/spec/features/projects/activity/user_sees_design_comment_spec.rb +++ b/spec/features/projects/activity/user_sees_design_comment_spec.rb @@ -12,7 +12,7 @@ RSpec.describe 'Projects > Activity > User sees design comment', :js do let_it_be(:design) { create(:design, issue: issue) } let(:design_activity) do - "#{commenter.name} #{commenter.to_reference} commented on design" + "#{commenter.name} #{commenter.to_reference} commented on design #{design.to_reference}" end let(:issue_activity) do diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb index 3382bdcd65f..d1e635f11c0 100644 --- a/spec/features/projects/badges/list_spec.rb +++ b/spec/features/projects/badges/list_spec.rb @@ -17,10 +17,10 @@ RSpec.describe 'list of badges' do expect(page).to have_content 'Markdown' expect(page).to have_content 'HTML' expect(page).to have_content 'AsciiDoc' - expect(page).to have_css('.highlight', count: 3) + expect(page).to have_css('.js-syntax-highlight', count: 3) expect(page).to have_xpath("//img[@alt='pipeline status']") - page.within('.highlight', match: :first) do + page.within('.js-syntax-highlight', match: :first) do expect(page).to have_content 'badges/master/pipeline.svg' end end @@ -32,10 +32,10 @@ RSpec.describe 'list of badges' do expect(page).to have_content 'Markdown' expect(page).to have_content 'HTML' expect(page).to have_content 'AsciiDoc' - expect(page).to have_css('.highlight', count: 3) + expect(page).to have_css('.js-syntax-highlight', count: 3) expect(page).to have_xpath("//img[@alt='coverage report']") - page.within('.highlight', match: :first) do + page.within('.js-syntax-highlight', match: :first) do expect(page).to have_content 'badges/master/coverage.svg' end end diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb index 5aca994f53e..c30c8dda852 100644 --- a/spec/features/projects/blobs/edit_spec.rb +++ b/spec/features/projects/blobs/edit_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe 'Editing file blob', :js do include TreeHelper + include BlobSpecHelpers let(:project) { create(:project, :public, :repository) } let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') } @@ -20,9 +21,18 @@ RSpec.describe 'Editing file blob', :js do sign_in(user) end - def edit_and_commit(commit_changes: true) + def edit_and_commit(commit_changes: true, is_diff: false) + set_default_button('edit') + refresh wait_for_requests - find('.js-edit-blob').click + + if is_diff + first('.js-diff-more-actions').click + click_link('Edit in single-file editor') + else + click_link('Edit') + end + fill_editor(content: 'class NextFeature\\nend\\n') if commit_changes @@ -38,7 +48,7 @@ RSpec.describe 'Editing file blob', :js do context 'from MR diff' do before do visit diffs_project_merge_request_path(project, merge_request) - edit_and_commit + edit_and_commit(is_diff: true) end it 'returns me to the mr' do diff --git a/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb b/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb index a271a4f43a8..fda2992af8d 100644 --- a/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb +++ b/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb @@ -2,7 +2,9 @@ require 'spec_helper' -RSpec.describe 'User creates blob in new project', :js do +RSpec.describe 'User creates new blob', :js do + include WebIdeSpecHelpers + let(:user) { create(:user) } let(:project) { create(:project, :empty_repo) } @@ -12,16 +14,19 @@ RSpec.describe 'User creates blob in new project', :js do visit project_path(project) end - it 'allows the user to add a new file' do + it 'allows the user to add a new file in Web IDE' do click_link 'New file' - execute_script("monaco.editor.getModels()[0].setValue('Hello world')") + wait_for_requests + + ide_create_new_file('dummy-file', content: "Hello world\n") - fill_in(:file_name, with: 'dummy-file') + ide_commit - click_button('Commit changes') + click_button('Commit') - expect(page).to have_content('The file has been successfully created') + expect(page).to have_content('All changes are committed') + expect(project.repository.blob_at('master', 'dummy-file').data).to eql("Hello world\n") end end diff --git a/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb b/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb index 8b43687c71c..023e00a3e02 100644 --- a/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb +++ b/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb @@ -10,7 +10,7 @@ RSpec.describe 'User follows pipeline suggest nudge spec when feature is enabled describe 'viewing the new blob page' do before do - stub_feature_flags(suggest_pipeline: true) + stub_experiment_for_user(suggest_pipeline: true) sign_in(user) end diff --git a/spec/features/projects/branches/user_deletes_branch_spec.rb b/spec/features/projects/branches/user_deletes_branch_spec.rb index 21a1d31bad4..c480c41709c 100644 --- a/spec/features/projects/branches/user_deletes_branch_spec.rb +++ b/spec/features/projects/branches/user_deletes_branch_spec.rb @@ -17,7 +17,7 @@ RSpec.describe "User deletes branch", :js do fill_in("branch-search", with: "improve/awesome").native.send_keys(:enter) page.within(".js-branch-improve\\/awesome") do - accept_alert { find(".btn-remove").click } + accept_alert { find(".btn-danger").click } end wait_for_requests diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb index 0e2444c5434..dcad7ee66a3 100644 --- a/spec/features/projects/branches_spec.rb +++ b/spec/features/projects/branches_spec.rb @@ -21,11 +21,11 @@ RSpec.describe 'Branches' do before do # Add 4 stale branches (1..4).reverse_each do |i| - Timecop.freeze((threshold + i).ago) { create_file(message: "a commit in stale-#{i}", branch_name: "stale-#{i}") } + travel_to((threshold + i).ago) { create_file(message: "a commit in stale-#{i}", branch_name: "stale-#{i}") } end # Add 6 active branches (1..6).each do |i| - Timecop.freeze((threshold - i).ago) { create_file(message: "a commit in active-#{i}", branch_name: "active-#{i}") } + travel_to((threshold - i).ago) { create_file(message: "a commit in active-#{i}", branch_name: "active-#{i}") } end end @@ -101,7 +101,7 @@ RSpec.describe 'Branches' do visit project_branches_filtered_path(project, state: 'all') expect(all('.all-branches').last).to have_selector('li', count: 20) - accept_confirm { first('.js-branch-item .btn-remove').click } + accept_confirm { first('.js-branch-item .btn-danger').click } expect(all('.all-branches').last).to have_selector('li', count: 19) end @@ -163,7 +163,7 @@ RSpec.describe 'Branches' do expect(page).to have_content('fix') expect(find('.all-branches')).to have_selector('li', count: 1) - accept_confirm { find('.js-branch-fix .btn-remove').click } + accept_confirm { find('.js-branch-fix .btn-danger').click } expect(page).not_to have_content('fix') expect(find('.all-branches')).to have_selector('li', count: 0) diff --git a/spec/features/projects/ci/lint_spec.rb b/spec/features/projects/ci/lint_spec.rb index ce435151b84..eb2efb4357d 100644 --- a/spec/features/projects/ci/lint_spec.rb +++ b/spec/features/projects/ci/lint_spec.rb @@ -8,117 +8,88 @@ RSpec.describe 'CI Lint', :js do let(:project) { create(:project, :repository) } let(:user) { create(:user) } - shared_examples 'correct ci linting process' do - describe 'YAML parsing' do - shared_examples 'validates the YAML' do - before do - stub_feature_flags(ci_lint_vue: false) - click_on 'Validate' - end + let(:content_selector) { '.content .view-lines' } - context 'YAML is correct' do - let(:yaml_content) do - File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) - end + before do + stub_feature_flags(ci_lint_vue: false) + project.add_developer(user) + sign_in(user) - it 'parses Yaml and displays the jobs' do - expect(page).to have_content('Status: syntax is correct') + visit project_ci_lint_path(project) + editor_set_value(yaml_content) - within "table" do - aggregate_failures do - expect(page).to have_content('Job - rspec') - expect(page).to have_content('Job - spinach') - expect(page).to have_content('Deploy Job - staging') - expect(page).to have_content('Deploy Job - production') - end - end - end - end + wait_for('YAML content') do + find(content_selector).text.present? + end + end - context 'YAML is incorrect' do - let(:yaml_content) { 'value: cannot have :' } + describe 'YAML parsing' do + shared_examples 'validates the YAML' do + before do + stub_feature_flags(ci_lint_vue: false) + click_on 'Validate' + end - it 'displays information about an error' do - expect(page).to have_content('Status: syntax is incorrect') - expect(page).to have_selector(content_selector, text: yaml_content) - end + context 'YAML is correct' do + let(:yaml_content) do + File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) end - end - it_behaves_like 'validates the YAML' + it 'parses Yaml and displays the jobs' do + expect(page).to have_content('Status: syntax is correct') - context 'when Dry Run is checked' do - before do - check 'Simulate a pipeline created for the default branch' + within "table" do + aggregate_failures do + expect(page).to have_content('Job - rspec') + expect(page).to have_content('Job - spinach') + expect(page).to have_content('Deploy Job - staging') + expect(page).to have_content('Deploy Job - production') + end + end end - - it_behaves_like 'validates the YAML' end - describe 'YAML revalidate' do - let(:yaml_content) { 'my yaml content' } + context 'YAML is incorrect' do + let(:yaml_content) { 'value: cannot have :' } - it 'loads previous YAML content after validation' do - expect(page).to have_field('content', with: 'my yaml content', visible: false, type: 'textarea') + it 'displays information about an error' do + expect(page).to have_content('Status: syntax is incorrect') + expect(page).to have_selector(content_selector, text: yaml_content) end end end - describe 'YAML clearing' do + it_behaves_like 'validates the YAML' + + context 'when Dry Run is checked' do before do - click_on 'Clear' + check 'Simulate a pipeline created for the default branch' end - context 'YAML is present' do - let(:yaml_content) do - File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) - end - - it 'YAML content is cleared' do - expect(page).to have_field('content', with: '', visible: false, type: 'textarea') - end - end + it_behaves_like 'validates the YAML' end - end - context 'with ACE editor' do - it_behaves_like 'correct ci linting process' do - let(:content_selector) { '.ace_content' } + describe 'YAML revalidate' do + let(:yaml_content) { 'my yaml content' } - before do - stub_feature_flags(monaco_ci: false) - stub_feature_flags(ci_lint_vue: false) - project.add_developer(user) - sign_in(user) - - visit project_ci_lint_path(project) - find('#ci-editor') - execute_script("ace.edit('ci-editor').setValue(#{yaml_content.to_json});") - - # Ace editor updates a hidden textarea and it happens asynchronously - wait_for('YAML content') do - find(content_selector).text.present? - end + it 'loads previous YAML content after validation' do + expect(page).to have_field('content', with: 'my yaml content', visible: false, type: 'textarea') end end end - context 'with Editor Lite' do - it_behaves_like 'correct ci linting process' do - let(:content_selector) { '.content .view-lines' } - - before do - stub_feature_flags(monaco_ci: true) - stub_feature_flags(ci_lint_vue: false) - project.add_developer(user) - sign_in(user) + describe 'YAML clearing' do + before do + click_on 'Clear' + end - visit project_ci_lint_path(project) - editor_set_value(yaml_content) + context 'YAML is present' do + let(:yaml_content) do + File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + end - wait_for('YAML content') do - find(content_selector).text.present? - end + it 'YAML content is cleared' do + expect(page).to have_field('content', with: '', visible: false, type: 'textarea') end end end diff --git a/spec/features/projects/clusters/eks_spec.rb b/spec/features/projects/clusters/eks_spec.rb index c5feef6c6f3..9f3f331cfab 100644 --- a/spec/features/projects/clusters/eks_spec.rb +++ b/spec/features/projects/clusters/eks_spec.rb @@ -19,7 +19,7 @@ RSpec.describe 'AWS EKS Cluster', :js do before do visit project_clusters_path(project) - click_link 'Add Kubernetes cluster' + click_link 'Integrate with a cluster certificate' end context 'when user creates a cluster on AWS EKS' do diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index 04339d20d77..a0519d88532 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -33,7 +33,7 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do before do visit project_clusters_path(project) - click_link 'Add Kubernetes cluster' + click_link 'Integrate with a cluster certificate' click_link 'Create new cluster' click_link 'Google GKE' end @@ -143,7 +143,7 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do before do visit project_clusters_path(project) - click_link 'Add Kubernetes cluster' + click_link 'Connect cluster with certificate' click_link 'Connect existing cluster' end @@ -162,7 +162,7 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do it 'user sees creation form with the successful message' do expect(page).to have_content('Kubernetes cluster integration was successfully removed.') - expect(page).to have_link('Add Kubernetes cluster') + expect(page).to have_link('Integrate with a cluster certificate') end end end @@ -178,7 +178,7 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do end it 'user sees offer on cluster create page' do - click_link 'Add Kubernetes cluster' + click_link 'Integrate with a cluster certificate' expect(page).to have_css('.gcp-signup-offer') end @@ -192,10 +192,10 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do it 'user does not see offer after dismissing' do expect(page).to have_css('.gcp-signup-offer') - find('.gcp-signup-offer .close').click + find('.gcp-signup-offer .js-close').click wait_for_requests - click_link 'Add Kubernetes cluster' + click_link 'Integrate with a cluster certificate' expect(page).not_to have_css('.gcp-signup-offer') end diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb index 9d0dc65093e..748eba558aa 100644 --- a/spec/features/projects/clusters/user_spec.rb +++ b/spec/features/projects/clusters/user_spec.rb @@ -25,7 +25,7 @@ RSpec.describe 'User Cluster', :js do before do visit project_clusters_path(project) - click_link 'Add Kubernetes cluster' + click_link 'Integrate with a cluster certificate' click_link 'Connect existing cluster' end @@ -52,6 +52,10 @@ RSpec.describe 'User Cluster', :js do it 'user sees RBAC is enabled by default' do expect(page).to have_checked_field('RBAC-enabled cluster') end + + it 'user sees namespace per environment is enabled by default' do + expect(page).to have_checked_field('Namespace per environment') + end end context 'when user filled form with invalid parameters' do @@ -112,7 +116,7 @@ RSpec.describe 'User Cluster', :js do it 'user sees creation form with the successful message' do expect(page).to have_content('Kubernetes cluster integration was successfully removed.') - expect(page).to have_link('Add Kubernetes cluster') + expect(page).to have_link('Integrate with a cluster certificate') end end end diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb index d674fbc457e..6c6e65005f6 100644 --- a/spec/features/projects/clusters_spec.rb +++ b/spec/features/projects/clusters_spec.rb @@ -11,7 +11,6 @@ RSpec.describe 'Clusters', :js do before do project.add_maintainer(user) gitlab_sign_in(user) - stub_feature_flags(clusters_list_redesign: false) end context 'when user does not have a cluster and visits cluster index page' do @@ -20,7 +19,7 @@ RSpec.describe 'Clusters', :js do end it 'sees empty state' do - expect(page).to have_link('Add Kubernetes cluster') + expect(page).to have_link('Integrate with a cluster certificate') expect(page).to have_selector('.empty-state') end end @@ -42,7 +41,7 @@ RSpec.describe 'Clusters', :js do context 'when user filled form with environment scope' do before do - click_link 'Add Kubernetes cluster' + click_link 'Connect cluster with certificate' click_link 'Connect existing cluster' fill_in 'cluster_name', with: 'staging-cluster' fill_in 'cluster_environment_scope', with: 'staging/*' @@ -71,7 +70,7 @@ RSpec.describe 'Clusters', :js do context 'when user updates duplicated environment scope' do before do - click_link 'Add Kubernetes cluster' + click_link 'Connect cluster with certificate' click_link 'Connect existing cluster' fill_in 'cluster_name', with: 'staging-cluster' fill_in 'cluster_environment_scope', with: '*' @@ -117,7 +116,7 @@ RSpec.describe 'Clusters', :js do context 'when user filled form with environment scope' do before do - click_link 'Add Kubernetes cluster' + click_link 'Connect cluster with certificate' click_link 'Create new cluster' click_link 'Google GKE' @@ -162,7 +161,7 @@ RSpec.describe 'Clusters', :js do context 'when user updates duplicated environment scope' do before do - click_link 'Add Kubernetes cluster' + click_link 'Connect cluster with certificate' click_link 'Create new cluster' click_link 'Google GKE' @@ -196,8 +195,7 @@ RSpec.describe 'Clusters', :js do end it 'user sees a table with one cluster' do - # One is the header row, the other the cluster row - expect(page).to have_selector('.gl-responsive-table-row', count: 2) + expect(page).to have_selector('[data-testid="cluster_list_table"] tbody tr', count: 1) end context 'when user clicks on a cluster' do @@ -216,7 +214,7 @@ RSpec.describe 'Clusters', :js do before do visit project_clusters_path(project) - click_link 'Add Kubernetes cluster' + click_link 'Integrate with a cluster certificate' click_link 'Create new cluster' end diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb index f97abc5bd8b..00ec9d49a10 100644 --- a/spec/features/projects/commit/builds_spec.rb +++ b/spec/features/projects/commit/builds_spec.rb @@ -19,7 +19,7 @@ RSpec.describe 'project commit pipelines', :js do context 'when no builds triggered yet' do it 'shows the ID of the first pipeline' do - page.within('.table-holder') do + page.within('.pipelines .ci-table') do expect(page).to have_content project.ci_pipelines[0].id # pipeline ids end end diff --git a/spec/features/projects/commit/user_comments_on_commit_spec.rb b/spec/features/projects/commit/user_comments_on_commit_spec.rb index 87a022d74a3..0fa4975bb25 100644 --- a/spec/features/projects/commit/user_comments_on_commit_spec.rb +++ b/spec/features/projects/commit/user_comments_on_commit_spec.rb @@ -6,19 +6,22 @@ RSpec.describe "User comments on commit", :js do include Spec::Support::Helpers::Features::NotesHelpers include RepoHelpers - let(:project) { create(:project, :repository) } - let(:user) { create(:user) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { create(:user) } let(:comment_text) { "XML attached" } - before do - sign_in(user) + before_all do project.add_developer(user) + end - visit(project_commit_path(project, sample_commit.id)) + before do + sign_in(user) end context "when adding new comment" do it "adds comment" do + visit(project_commit_path(project, sample_commit.id)) + emoji_code = ":+1:" page.within(".js-main-target-form") do @@ -57,6 +60,8 @@ RSpec.describe "User comments on commit", :js do context "when editing comment" do before do + visit(project_commit_path(project, sample_commit.id)) + add_note(comment_text) end @@ -87,6 +92,8 @@ RSpec.describe "User comments on commit", :js do context "when deleting comment" do before do + visit(project_commit_path(project, sample_commit.id)) + add_note(comment_text) end @@ -108,4 +115,35 @@ RSpec.describe "User comments on commit", :js do expect(page).not_to have_css(".note") end end + + context 'when checking task lists' do + let(:note_with_task) do + <<-EOT.strip_heredoc + + - [ ] Task 1 + EOT + end + + before do + create(:note_on_commit, project: project, commit_id: sample_commit.id, note: note_with_task, author: user) + create(:note_on_commit, project: project, commit_id: sample_commit.id, note: note_with_task, author: user) + + visit(project_commit_path(project, sample_commit.id)) + end + + it 'allows the tasks to be checked' do + expect(page).to have_selector('li.task-list-item', count: 2) + expect(page).to have_selector('li.task-list-item input[checked]', count: 0) + + all('.task-list-item-checkbox').each do |checkbox| + checkbox.click + end + wait_for_requests + + visit(project_commit_path(project, sample_commit.id)) + + expect(page).to have_selector('li.task-list-item', count: 2) + expect(page).to have_selector('li.task-list-item input[checked]', count: 2) + end + end end diff --git a/spec/features/projects/compare_spec.rb b/spec/features/projects/compare_spec.rb index 865ae3ad8cb..e387ea4d473 100644 --- a/spec/features/projects/compare_spec.rb +++ b/spec/features/projects/compare_spec.rb @@ -113,7 +113,7 @@ RSpec.describe "Compare", :js do click_button('Compare') - page.within('.alert') do + page.within('.gl-alert') do expect(page).to have_text("Too many changes to show. To preserve performance only 3 of 3+ files are displayed.") end end diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index fa10e429af2..1d7be7fa7a3 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -333,7 +333,7 @@ RSpec.describe 'Environment' do visit project_branches_filtered_path(project, state: 'all', search: 'feature') remove_branch_with_hooks(project, user, 'feature') do - page.within('.js-branch-feature') { find('a.btn-remove').click } + page.within('.js-branch-feature') { find('a.btn-danger').click } end visit_environment(environment) diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index 7f2ef61bcbe..8c032660726 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -372,7 +372,7 @@ RSpec.describe 'Environments page', :js do let(:role) { :developer } it 'developer creates a new environment with a valid name' do - within(".top-area") { click_link 'New environment' } + within(".environments-section") { click_link 'New environment' } fill_in('Name', with: 'production') click_on 'Save' @@ -380,7 +380,7 @@ RSpec.describe 'Environments page', :js do end it 'developer creates a new environmetn with invalid name' do - within(".top-area") { click_link 'New environment' } + within(".environments-section") { click_link 'New environment' } fill_in('Name', with: 'name,with,commas') click_on 'Save' diff --git a/spec/features/projects/feature_flag_user_lists/user_deletes_feature_flag_user_list_spec.rb b/spec/features/projects/feature_flag_user_lists/user_deletes_feature_flag_user_list_spec.rb new file mode 100644 index 00000000000..2a81c706525 --- /dev/null +++ b/spec/features/projects/feature_flag_user_lists/user_deletes_feature_flag_user_list_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User deletes feature flag user list', :js do + let_it_be(:project) { create(:project) } + let_it_be(:developer) { create(:user) } + + before do + project.add_developer(developer) + sign_in(developer) + end + + context 'with a list' do + before do + create(:operations_feature_flag_user_list, project: project, name: 'My List') + end + + it 'deletes the list' do + visit(project_feature_flags_path(project, scope: 'userLists')) + + delete_user_list_button.click + delete_user_list_modal_confirmation_button.click + + expect(page).to have_text('Lists 0') + end + end + + context 'with a list that is in use' do + before do + list = create(:operations_feature_flag_user_list, project: project, name: 'My List') + feature_flag = create(:operations_feature_flag, :new_version_flag, project: project) + create(:operations_strategy, feature_flag: feature_flag, name: 'gitlabUserList', user_list: list) + end + + it 'does not delete the list' do + visit(project_feature_flags_path(project, scope: 'userLists')) + + delete_user_list_button.click + delete_user_list_modal_confirmation_button.click + + expect(page).to have_text('User list is associated with a strategy') + expect(page).to have_text('Lists 1') + expect(page).to have_text('My List') + + alert_dismiss_button.click + + expect(page).not_to have_text('User list is associated with a strategy') + end + end + + def delete_user_list_button + find("button[data-testid='delete-user-list']") + end + + def delete_user_list_modal_confirmation_button + find("button[data-testid='modal-confirm']") + end + + def alert_dismiss_button + find("div[data-testid='serverErrors'] button") + end +end diff --git a/spec/features/projects/feature_flag_user_lists/user_edits_feature_flag_user_list_spec.rb b/spec/features/projects/feature_flag_user_lists/user_edits_feature_flag_user_list_spec.rb new file mode 100644 index 00000000000..b37c2780827 --- /dev/null +++ b/spec/features/projects/feature_flag_user_lists/user_edits_feature_flag_user_list_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User edits feature flag user list', :js do + let_it_be(:project) { create(:project) } + let_it_be(:developer) { create(:user) } + + before do + project.add_developer(developer) + sign_in(developer) + end + + it 'prefills the edit form with the list name' do + list = create(:operations_feature_flag_user_list, project: project, name: 'My List Name') + + visit(edit_project_feature_flags_user_list_path(project, list)) + + expect(page).to have_field 'Name', with: 'My List Name' + end +end diff --git a/spec/features/projects/feature_flag_user_lists/user_sees_feature_flag_user_list_details_spec.rb b/spec/features/projects/feature_flag_user_lists/user_sees_feature_flag_user_list_details_spec.rb new file mode 100644 index 00000000000..dfebe6408bd --- /dev/null +++ b/spec/features/projects/feature_flag_user_lists/user_sees_feature_flag_user_list_details_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User sees feature flag user list details', :js do + let_it_be(:project) { create(:project) } + let_it_be(:developer) { create(:user) } + + before do + project.add_developer(developer) + sign_in(developer) + end + + it 'displays the list name' do + list = create(:operations_feature_flag_user_list, project: project, name: 'My List') + + visit(project_feature_flags_user_list_path(project, list)) + + expect(page).to have_text('My List') + end +end diff --git a/spec/features/projects/feature_flags/user_creates_feature_flag_spec.rb b/spec/features/projects/feature_flags/user_creates_feature_flag_spec.rb new file mode 100644 index 00000000000..830dda737b0 --- /dev/null +++ b/spec/features/projects/feature_flags/user_creates_feature_flag_spec.rb @@ -0,0 +1,200 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User creates feature flag', :js do + include FeatureFlagHelpers + + let(:user) { create(:user) } + let(:project) { create(:project, namespace: user.namespace) } + + before do + project.add_developer(user) + stub_feature_flags(feature_flag_permissions: false) + sign_in(user) + end + + it 'user creates a flag enabled for user ids' do + visit(new_project_feature_flag_path(project)) + set_feature_flag_info('test_feature', 'Test feature') + within_strategy_row(1) do + select 'User IDs', from: 'Type' + fill_in 'User IDs', with: 'user1, user2' + environment_plus_button.click + environment_search_input.set('production') + environment_search_results.first.click + end + click_button 'Create feature flag' + + expect_user_to_see_feature_flags_index_page + expect(page).to have_text('test_feature') + end + + it 'user creates a flag with default environment scopes' do + visit(new_project_feature_flag_path(project)) + set_feature_flag_info('test_flag', 'Test flag') + within_strategy_row(1) do + select 'All users', from: 'Type' + end + click_button 'Create feature flag' + + expect_user_to_see_feature_flags_index_page + expect(page).to have_text('test_flag') + + edit_feature_flag_button.click + + within_strategy_row(1) do + expect(page).to have_text('All users') + expect(page).to have_text('All environments') + end + end + + it 'removes the correct strategy when a strategy is deleted' do + visit(new_project_feature_flag_path(project)) + click_button 'Add strategy' + within_strategy_row(1) do + select 'All users', from: 'Type' + end + within_strategy_row(2) do + select 'Percent of users', from: 'Type' + end + within_strategy_row(1) do + delete_strategy_button.click + end + + within_strategy_row(1) do + expect(page).to have_select('Type', selected: 'Percent of users') + end + end + + context 'with new version flags disabled' do + before do + stub_feature_flags(feature_flags_new_version: false) + end + + context 'when creates without changing scopes' do + before do + visit(new_project_feature_flag_path(project)) + set_feature_flag_info('ci_live_trace', 'For live trace') + click_button 'Create feature flag' + expect(page).to have_current_path(project_feature_flags_path(project)) + end + + it 'shows the created feature flag' do + within_feature_flag_row(1) do + expect(page.find('.feature-flag-name')).to have_content('ci_live_trace') + expect_status_toggle_button_to_be_checked + + within_feature_flag_scopes do + expect(page.find('[data-qa-selector="feature-flag-scope-info-badge"]:nth-child(1)')).to have_content('*') + end + end + end + end + + context 'when creates with disabling the default scope' do + before do + visit(new_project_feature_flag_path(project)) + set_feature_flag_info('ci_live_trace', 'For live trace') + + within_scope_row(1) do + within_status { find('.project-feature-toggle').click } + end + + click_button 'Create feature flag' + end + + it 'shows the created feature flag' do + within_feature_flag_row(1) do + expect(page.find('.feature-flag-name')).to have_content('ci_live_trace') + expect_status_toggle_button_to_be_checked + + within_feature_flag_scopes do + expect(page.find('[data-qa-selector="feature-flag-scope-muted-badge"]:nth-child(1)')).to have_content('*') + end + end + end + end + + context 'when creates with an additional scope' do + before do + visit(new_project_feature_flag_path(project)) + set_feature_flag_info('mr_train', '') + + within_scope_row(2) do + within_environment_spec do + find('.js-env-search > input').set("review/*") + find('.js-create-button').click + end + end + + within_scope_row(2) do + within_status { find('.project-feature-toggle').click } + end + + click_button 'Create feature flag' + end + + it 'shows the created feature flag' do + within_feature_flag_row(1) do + expect(page.find('.feature-flag-name')).to have_content('mr_train') + expect_status_toggle_button_to_be_checked + + within_feature_flag_scopes do + expect(page.find('[data-qa-selector="feature-flag-scope-info-badge"]:nth-child(1)')).to have_content('*') + expect(page.find('[data-qa-selector="feature-flag-scope-info-badge"]:nth-child(2)')).to have_content('review/*') + end + end + end + end + + context 'when searches an environment name for scope creation' do + let!(:environment) { create(:environment, name: 'production', project: project) } + + before do + visit(new_project_feature_flag_path(project)) + set_feature_flag_info('mr_train', '') + + within_scope_row(2) do + within_environment_spec do + find('.js-env-search > input').set('prod') + click_button 'production' + end + end + + click_button 'Create feature flag' + end + + it 'shows the created feature flag' do + within_feature_flag_row(1) do + expect(page.find('.feature-flag-name')).to have_content('mr_train') + expect_status_toggle_button_to_be_checked + + within_feature_flag_scopes do + expect(page.find('[data-qa-selector="feature-flag-scope-info-badge"]:nth-child(1)')).to have_content('*') + expect(page.find('[data-qa-selector="feature-flag-scope-muted-badge"]:nth-child(2)')).to have_content('production') + end + end + end + end + end + + private + + def set_feature_flag_info(name, description) + fill_in 'Name', with: name + fill_in 'Description', with: description + end + + def environment_plus_button + find('.js-new-environments-dropdown') + end + + def environment_search_input + find('.js-new-environments-dropdown input') + end + + def environment_search_results + all('.js-new-environments-dropdown button.dropdown-item') + end +end diff --git a/spec/features/projects/feature_flags/user_deletes_feature_flag_spec.rb b/spec/features/projects/feature_flags/user_deletes_feature_flag_spec.rb new file mode 100644 index 00000000000..581709aacee --- /dev/null +++ b/spec/features/projects/feature_flags/user_deletes_feature_flag_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User deletes feature flag', :js do + include FeatureFlagHelpers + + let(:user) { create(:user) } + let(:project) { create(:project, namespace: user.namespace) } + + let!(:feature_flag) do + create_flag(project, 'ci_live_trace', false, + description: 'For live trace feature') + end + + before do + project.add_developer(user) + stub_feature_flags(feature_flag_permissions: false) + sign_in(user) + + visit(project_feature_flags_path(project)) + + find('.js-feature-flag-delete-button').click + click_button('Delete feature flag') + expect(page).to have_current_path(project_feature_flags_path(project)) + end + + it 'user does not see feature flag' do + expect(page).to have_no_content('ci_live_trace') + end +end diff --git a/spec/features/projects/feature_flags/user_sees_feature_flag_list_spec.rb b/spec/features/projects/feature_flags/user_sees_feature_flag_list_spec.rb new file mode 100644 index 00000000000..750f4dc5ef4 --- /dev/null +++ b/spec/features/projects/feature_flags/user_sees_feature_flag_list_spec.rb @@ -0,0 +1,147 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User sees feature flag list', :js do + include FeatureFlagHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, namespace: user.namespace) } + + before_all do + project.add_developer(user) + end + + before do + sign_in(user) + end + + context 'with legacy feature flags' do + before do + create_flag(project, 'ci_live_trace', false).tap do |feature_flag| + create_scope(feature_flag, 'review/*', true) + end + create_flag(project, 'drop_legacy_artifacts', false) + create_flag(project, 'mr_train', true).tap do |feature_flag| + create_scope(feature_flag, 'production', false) + end + stub_feature_flags(feature_flags_legacy_read_only_override: false) + end + + it 'user sees the first flag' do + visit(project_feature_flags_path(project)) + + within_feature_flag_row(1) do + expect(page.find('.js-feature-flag-id')).to have_content('^1') + expect(page.find('.feature-flag-name')).to have_content('ci_live_trace') + expect_status_toggle_button_not_to_be_checked + + within_feature_flag_scopes do + expect(page.find('[data-qa-selector="feature-flag-scope-muted-badge"]:nth-child(1)')).to have_content('*') + expect(page.find('[data-qa-selector="feature-flag-scope-info-badge"]:nth-child(2)')).to have_content('review/*') + end + end + end + + it 'user sees the second flag' do + visit(project_feature_flags_path(project)) + + within_feature_flag_row(2) do + expect(page.find('.js-feature-flag-id')).to have_content('^2') + expect(page.find('.feature-flag-name')).to have_content('drop_legacy_artifacts') + expect_status_toggle_button_not_to_be_checked + + within_feature_flag_scopes do + expect(page.find('[data-qa-selector="feature-flag-scope-muted-badge"]:nth-child(1)')).to have_content('*') + end + end + end + + it 'user sees the third flag' do + visit(project_feature_flags_path(project)) + + within_feature_flag_row(3) do + expect(page.find('.js-feature-flag-id')).to have_content('^3') + expect(page.find('.feature-flag-name')).to have_content('mr_train') + expect_status_toggle_button_to_be_checked + + within_feature_flag_scopes do + expect(page.find('[data-qa-selector="feature-flag-scope-info-badge"]:nth-child(1)')).to have_content('*') + expect(page.find('[data-qa-selector="feature-flag-scope-muted-badge"]:nth-child(2)')).to have_content('production') + end + end + end + + it 'user sees the status toggle disabled' do + visit(project_feature_flags_path(project)) + + within_feature_flag_row(1) do + expect_status_toggle_button_to_be_disabled + end + end + + context 'when legacy feature flags are not read-only' do + before do + stub_feature_flags(feature_flags_legacy_read_only: false) + end + + it 'user updates the status toggle' do + visit(project_feature_flags_path(project)) + + within_feature_flag_row(1) do + status_toggle_button.click + + expect_status_toggle_button_to_be_checked + end + end + end + + context 'when legacy feature flags are read-only but the override is active for a project' do + before do + stub_feature_flags( + feature_flags_legacy_read_only: true, + feature_flags_legacy_read_only_override: project + ) + end + + it 'user updates the status toggle' do + visit(project_feature_flags_path(project)) + + within_feature_flag_row(1) do + status_toggle_button.click + + expect_status_toggle_button_to_be_checked + end + end + end + end + + context 'with new version flags' do + before do + create(:operations_feature_flag, :new_version_flag, project: project, + name: 'my_flag', active: false) + end + + it 'user updates the status toggle' do + visit(project_feature_flags_path(project)) + + within_feature_flag_row(1) do + status_toggle_button.click + + expect_status_toggle_button_to_be_checked + end + end + end + + context 'when there are no feature flags' do + before do + visit(project_feature_flags_path(project)) + end + + it 'shows empty page' do + expect(page).to have_text 'Get started with feature flags' + expect(page).to have_selector('.btn-success', text: 'New feature flag') + expect(page).to have_selector('[data-qa-selector="configure_feature_flags_button"]', text: 'Configure') + end + end +end diff --git a/spec/features/projects/feature_flags/user_updates_feature_flag_spec.rb b/spec/features/projects/feature_flags/user_updates_feature_flag_spec.rb new file mode 100644 index 00000000000..bc2d63e1953 --- /dev/null +++ b/spec/features/projects/feature_flags/user_updates_feature_flag_spec.rb @@ -0,0 +1,195 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User updates feature flag', :js do + include FeatureFlagHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, namespace: user.namespace) } + + before_all do + project.add_developer(user) + end + + before do + stub_feature_flags( + feature_flag_permissions: false, + feature_flags_legacy_read_only_override: false + ) + sign_in(user) + end + + context 'with a new version feature flag' do + let!(:feature_flag) do + create_flag(project, 'test_flag', false, version: Operations::FeatureFlag.versions['new_version_flag'], + description: 'For testing') + end + + let!(:strategy) do + create(:operations_strategy, feature_flag: feature_flag, + name: 'default', parameters: {}) + end + + let!(:scope) do + create(:operations_scope, strategy: strategy, environment_scope: '*') + end + + it 'user adds a second strategy' do + visit(edit_project_feature_flag_path(project, feature_flag)) + + wait_for_requests + + click_button 'Add strategy' + within_strategy_row(2) do + select 'Percent of users', from: 'Type' + fill_in 'Percentage', with: '15' + end + click_button 'Save changes' + + edit_feature_flag_button.click + + within_strategy_row(1) do + expect(page).to have_text 'All users' + expect(page).to have_text 'All environments' + end + within_strategy_row(2) do + expect(page).to have_text 'Percent of users' + expect(page).to have_field 'Percentage', with: '15' + expect(page).to have_text 'All environments' + end + end + + it 'user toggles the flag on' do + visit(edit_project_feature_flag_path(project, feature_flag)) + status_toggle_button.click + click_button 'Save changes' + + within_feature_flag_row(1) do + expect_status_toggle_button_to_be_checked + end + end + end + + context 'with a legacy feature flag' do + let!(:feature_flag) do + create_flag(project, 'ci_live_trace', true, + description: 'For live trace feature') + end + + let!(:scope) { create_scope(feature_flag, 'review/*', true) } + + context 'when legacy flags are editable' do + before do + stub_feature_flags(feature_flags_legacy_read_only: false) + + visit(edit_project_feature_flag_path(project, feature_flag)) + end + + it 'user sees persisted default scope' do + within_scope_row(1) do + within_environment_spec do + expect(page).to have_content('* (All Environments)') + end + + within_status do + expect(find('.project-feature-toggle')['aria-label']) + .to eq('Toggle Status: ON') + end + end + end + + context 'when user updates the status of a scope' do + before do + within_scope_row(2) do + within_status { find('.project-feature-toggle').click } + end + + click_button 'Save changes' + expect(page).to have_current_path(project_feature_flags_path(project)) + end + + it 'shows the updated feature flag' do + within_feature_flag_row(1) do + expect(page.find('.feature-flag-name')).to have_content('ci_live_trace') + expect_status_toggle_button_to_be_checked + + within_feature_flag_scopes do + expect(page.find('.badge:nth-child(1)')).to have_content('*') + expect(page.find('.badge:nth-child(1)')['class']).to include('badge-info') + expect(page.find('.badge:nth-child(2)')).to have_content('review/*') + expect(page.find('.badge:nth-child(2)')['class']).to include('badge-muted') + end + end + end + end + + context 'when user adds a new scope' do + before do + within_scope_row(3) do + within_environment_spec do + find('.js-env-search > input').set('production') + find('.js-create-button').click + end + end + + click_button 'Save changes' + expect(page).to have_current_path(project_feature_flags_path(project)) + end + + it 'shows the newly created scope' do + within_feature_flag_row(1) do + within_feature_flag_scopes do + expect(page.find('.badge:nth-child(3)')).to have_content('production') + expect(page.find('.badge:nth-child(3)')['class']).to include('badge-muted') + end + end + end + end + + context 'when user deletes a scope' do + before do + within_scope_row(2) do + within_delete { find('.js-delete-scope').click } + end + + click_button 'Save changes' + expect(page).to have_current_path(project_feature_flags_path(project)) + end + + it 'shows the updated feature flag' do + within_feature_flag_row(1) do + within_feature_flag_scopes do + expect(page).to have_css('.badge:nth-child(1)') + expect(page).not_to have_css('.badge:nth-child(2)') + end + end + end + end + end + + context 'when legacy flags are read-only' do + it 'the user cannot edit the flag' do + visit(edit_project_feature_flag_path(project, feature_flag)) + + expect(page).to have_text 'This feature flag is read-only, and it will be removed in 14.0.' + expect(page).to have_css('button.js-ff-submit.disabled') + end + end + + context 'when legacy flags are read-only, but the override is active for one project' do + it 'the user can edit the flag' do + stub_feature_flags(feature_flags_legacy_read_only_override: project) + + visit(edit_project_feature_flag_path(project, feature_flag)) + status_toggle_button.click + click_button 'Save changes' + + expect(page).to have_current_path(project_feature_flags_path(project)) + within_feature_flag_row(1) do + expect_status_toggle_button_not_to_be_checked + end + end + end + end +end diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb index 8d3ca9d9fd1..467adb25a17 100644 --- a/spec/features/projects/features_visibility_spec.rb +++ b/spec/features/projects/features_visibility_spec.rb @@ -201,7 +201,7 @@ RSpec.describe 'Edit Project Settings' do visit project_path(project) - expect(page).to have_content "Customize your workflow!" + expect(page).to have_content "joined project" end it "hides project activity tabs" do diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb index eed1e7aaf1b..d28e31c08dc 100644 --- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb +++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Projects > Files > Project owner sees a link to create a license file in empty project', :js do + include WebIdeSpecHelpers + let(:project) { create(:project_empty_repo) } let(:project_maintainer) { project.owner } @@ -10,36 +12,35 @@ RSpec.describe 'Projects > Files > Project owner sees a link to create a license sign_in(project_maintainer) end - it 'project maintainer creates a license file from a template' do + it 'allows project maintainer creates a license file from a template in Web IDE' do visit project_path(project) click_on 'Add LICENSE' - expect(page).to have_content('New file') - expect(current_path).to eq( - project_new_blob_path(project, 'master')) - expect(find('#file_name').value).to eq('LICENSE') - expect(page).to have_selector('.license-selector') + expect(current_path).to eq("/-/ide/project/#{project.full_path}/edit/master/-/LICENSE") + + expect(page).to have_selector('.qa-file-templates-bar') select_template('MIT License') - file_content = first('.file-editor') - expect(file_content).to have_content('MIT License') - expect(file_content).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}") + expect(ide_editor_value).to have_content('MIT License') + expect(ide_editor_value).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}") + + ide_commit + + click_button('Commit') + + expect(current_path).to eq("/-/ide/project/#{project.full_path}/tree/master/-/") - fill_in :commit_message, with: 'Add a LICENSE file', visible: true - click_button 'Commit changes' + expect(page).to have_content('All changes are committed') - expect(current_path).to eq( - project_blob_path(project, 'master/LICENSE')) - expect(page).to have_content('MIT License') - expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}") + license_file = project.repository.blob_at('master', 'LICENSE').data + expect(license_file).to have_content('MIT License') + expect(license_file).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}") end def select_template(template) - page.within('.js-license-selector-wrap') do - click_button 'Apply a template' - click_link template - wait_for_requests - end + click_button 'Choose a template...' + click_button template + wait_for_requests end end diff --git a/spec/features/projects/files/user_browses_lfs_files_spec.rb b/spec/features/projects/files/user_browses_lfs_files_spec.rb index ecc56b794b2..3be5ab64834 100644 --- a/spec/features/projects/files/user_browses_lfs_files_spec.rb +++ b/spec/features/projects/files/user_browses_lfs_files_spec.rb @@ -66,10 +66,30 @@ RSpec.describe 'Projects > Files > User browses LFS files' do expect(page).to have_content('History') expect(page).to have_content('Permalink') expect(page).to have_content('Replace') + expect(page).to have_link('Download') + expect(page).not_to have_content('Annotate') expect(page).not_to have_content('Blame') - expect(page).not_to have_content('Edit') - expect(page).to have_link('Download') + + expect(page).not_to have_selector(:link_or_button, text: /^Edit$/) + expect(page).to have_selector(:link_or_button, 'Edit in Web IDE') + end + end + + context 'when feature flag :consolidated_edit_button is off' do + before do + stub_feature_flags(consolidated_edit_button: false) + + click_link('files') + click_link('lfs') + click_link('lfs_object.iso') + end + + it 'does not show single file edit link' do + page.within('.content') do + expect(page).to have_selector(:link_or_button, 'Web IDE') + expect(page).not_to have_selector(:link_or_button, 'Edit') + end end end end diff --git a/spec/features/projects/files/user_creates_files_spec.rb b/spec/features/projects/files/user_creates_files_spec.rb index 39bc139656b..fd83547d064 100644 --- a/spec/features/projects/files/user_creates_files_spec.rb +++ b/spec/features/projects/files/user_creates_files_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Projects > Files > User creates files', :js do + include BlobSpecHelpers + let(:fork_message) do "You're not allowed to make changes to this project directly. "\ "A fork of this project has been created that you can make changes in, so you can submit a merge request." @@ -103,6 +105,8 @@ RSpec.describe 'Projects > Files > User creates files', :js do end it 'creates and commit a new file with new lines at the end of file' do + set_default_button('edit') + find('#editor') execute_script('monaco.editor.getModels()[0].setValue("Sample\n\n\n")') fill_in(:file_name, with: 'not_a_file.md') @@ -113,7 +117,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do expect(current_path).to eq(new_file_path) - find('.js-edit-blob').click + click_link('Edit') find('#editor') expect(evaluate_script('monaco.editor.getModels()[0].getValue()')).to eq("Sample\n\n\n") diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb index d3e075001c8..c18ff9ddbbc 100644 --- a/spec/features/projects/files/user_edits_files_spec.rb +++ b/spec/features/projects/files/user_edits_files_spec.rb @@ -4,6 +4,8 @@ require 'spec_helper' RSpec.describe 'Projects > Files > User edits files', :js do include ProjectForksHelper + include BlobSpecHelpers + let(:project) { create(:project, :repository, name: 'Shop') } let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } @@ -14,6 +16,10 @@ RSpec.describe 'Projects > Files > User edits files', :js do sign_in(user) end + after do + unset_default_button + end + shared_examples 'unavailable for an archived project' do it 'does not show the edit link for an archived project', :js do project.update!(archived: true) @@ -39,14 +45,15 @@ RSpec.describe 'Projects > Files > User edits files', :js do end it 'inserts a content of a file' do + set_default_button('edit') click_link('.gitignore') - find('.js-edit-blob').click + click_link_or_button('Edit') find('.file-editor', match: :first) find('#editor') - execute_script("monaco.editor.getModels()[0].setValue('*.rbca')") + set_editor_value('*.rbca') - expect(evaluate_script('monaco.editor.getModels()[0].getValue()')).to eq('*.rbca') + expect(editor_value).to eq('*.rbca') end it 'does not show the edit link if a file is binary' do @@ -60,12 +67,13 @@ RSpec.describe 'Projects > Files > User edits files', :js do end it 'commits an edited file' do + set_default_button('edit') click_link('.gitignore') - find('.js-edit-blob').click + click_link_or_button('Edit') find('.file-editor', match: :first) find('#editor') - execute_script("monaco.editor.getModels()[0].setValue('*.rbca')") + set_editor_value('*.rbca') fill_in(:commit_message, with: 'New commit message', visible: true) click_button('Commit changes') @@ -77,13 +85,14 @@ RSpec.describe 'Projects > Files > User edits files', :js do end it 'commits an edited file to a new branch' do + set_default_button('edit') click_link('.gitignore') - find('.js-edit-blob').click + click_link_or_button('Edit') find('.file-editor', match: :first) find('#editor') - execute_script("monaco.editor.getModels()[0].setValue('*.rbca')") + set_editor_value('*.rbca') fill_in(:commit_message, with: 'New commit message', visible: true) fill_in(:branch_name, with: 'new_branch_name', visible: true) click_button('Commit changes') @@ -96,12 +105,13 @@ RSpec.describe 'Projects > Files > User edits files', :js do end it 'shows the diff of an edited file' do + set_default_button('edit') click_link('.gitignore') - find('.js-edit-blob').click + click_link_or_button('Edit') find('.file-editor', match: :first) find('#editor') - execute_script("monaco.editor.getModels()[0].setValue('*.rbca')") + set_editor_value('*.rbca') click_link('Preview changes') expect(page).to have_css('.line_holder.new') @@ -118,8 +128,8 @@ RSpec.describe 'Projects > Files > User edits files', :js do end def expect_fork_prompt - expect(page).to have_link('Fork') - expect(page).to have_button('Cancel') + expect(page).to have_selector(:link_or_button, 'Fork') + expect(page).to have_selector(:link_or_button, 'Cancel') expect(page).to have_content( "You're not allowed to edit files in this project directly. "\ "Please fork this project, make your changes there, and submit a merge request." @@ -134,30 +144,32 @@ RSpec.describe 'Projects > Files > User edits files', :js do end it 'inserts a content of a file in a forked project', :sidekiq_might_not_need_inline do + set_default_button('edit') click_link('.gitignore') - click_button('Edit') + click_link_or_button('Edit') expect_fork_prompt - click_link('Fork') + click_link_or_button('Fork project') expect_fork_status find('.file-editor', match: :first) find('#editor') - execute_script("monaco.editor.getModels()[0].setValue('*.rbca')") + set_editor_value('*.rbca') - expect(evaluate_script('monaco.editor.getModels()[0].getValue()')).to eq('*.rbca') + expect(editor_value).to eq('*.rbca') end it 'opens the Web IDE in a forked project', :sidekiq_might_not_need_inline do + set_default_button('webide') click_link('.gitignore') - click_button('Web IDE') + click_link_or_button('Web IDE') expect_fork_prompt - click_link('Fork') + click_link_or_button('Fork project') expect_fork_status @@ -166,17 +178,17 @@ RSpec.describe 'Projects > Files > User edits files', :js do end it 'commits an edited file in a forked project', :sidekiq_might_not_need_inline do + set_default_button('edit') click_link('.gitignore') - find('.js-edit-blob').click + click_link_or_button('Edit') expect_fork_prompt - - click_link('Fork') + click_link_or_button('Fork project') find('.file-editor', match: :first) find('#editor') - execute_script("monaco.editor.getModels()[0].setValue('*.rbca')") + set_editor_value('*.rbca') fill_in(:commit_message, with: 'New commit message', visible: true) click_button('Commit changes') @@ -198,14 +210,14 @@ RSpec.describe 'Projects > Files > User edits files', :js do end it 'links to the forked project for editing', :sidekiq_might_not_need_inline do + set_default_button('edit') click_link('.gitignore') - find('.js-edit-blob').click + click_link_or_button('Edit') - expect(page).not_to have_link('Fork') - expect(page).not_to have_button('Cancel') + expect(page).not_to have_link('Fork project') find('#editor') - execute_script("monaco.editor.getModels()[0].setValue('*.rbca')") + set_editor_value('*.rbca') fill_in(:commit_message, with: 'Another commit', visible: true) click_button('Commit changes') @@ -224,5 +236,116 @@ RSpec.describe 'Projects > Files > User edits files', :js do let(:project) { project2 } end end + + context 'when feature flag :consolidated_edit_button is off' do + before do + stub_feature_flags(consolidated_edit_button: false) + end + + context 'when an user does not have write access', :js do + before do + project2.add_reporter(user) + visit(project2_tree_path_root_ref) + wait_for_requests + end + + it 'inserts a content of a file in a forked project', :sidekiq_might_not_need_inline do + set_default_button('edit') + click_link('.gitignore') + click_link_or_button('Edit') + + expect_fork_prompt + + click_link_or_button('Fork') + + expect_fork_status + + find('.file-editor', match: :first) + + find('#editor') + set_editor_value('*.rbca') + + expect(editor_value).to eq('*.rbca') + end + + it 'opens the Web IDE in a forked project', :sidekiq_might_not_need_inline do + set_default_button('webide') + click_link('.gitignore') + click_link_or_button('Web IDE') + + expect_fork_prompt + + click_link_or_button('Fork') + + expect_fork_status + + expect(page).to have_css('.ide-sidebar-project-title', text: "#{project2.name} #{user.namespace.full_path}/#{project2.path}") + expect(page).to have_css('.ide .multi-file-tab', text: '.gitignore') + end + + it 'commits an edited file in a forked project', :sidekiq_might_not_need_inline do + set_default_button('edit') + click_link('.gitignore') + click_link_or_button('Edit') + + expect_fork_prompt + + click_link_or_button('Fork') + + expect_fork_status + + find('.file-editor', match: :first) + + find('#editor') + set_editor_value('*.rbca') + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Commit changes') + + fork = user.fork_of(project2.reload) + + expect(current_path).to eq(project_new_merge_request_path(fork)) + + wait_for_requests + + expect(page).to have_content('New commit message') + end + + context 'when the user already had a fork of the project', :js do + let!(:forked_project) { fork_project(project2, user, namespace: user.namespace, repository: true) } + + before do + visit(project2_tree_path_root_ref) + wait_for_requests + end + + it 'links to the forked project for editing', :sidekiq_might_not_need_inline do + set_default_button('edit') + click_link('.gitignore') + click_link_or_button('Edit') + + expect(page).not_to have_link('Fork') + + find('#editor') + set_editor_value('*.rbca') + fill_in(:commit_message, with: 'Another commit', visible: true) + click_button('Commit changes') + + fork = user.fork_of(project2) + + expect(current_path).to eq(project_new_merge_request_path(fork)) + + wait_for_requests + + expect(page).to have_content('Another commit') + expect(page).to have_content("From #{forked_project.full_path}") + expect(page).to have_content("into #{project2.full_path}") + end + + it_behaves_like 'unavailable for an archived project' do + let(:project) { project2 } + end + end + end + end end end diff --git a/spec/features/projects/issues/design_management/user_links_to_designs_in_issue_spec.rb b/spec/features/projects/issues/design_management/user_links_to_designs_in_issue_spec.rb index 8d5e99d7e2b..78fb470d4ea 100644 --- a/spec/features/projects/issues/design_management/user_links_to_designs_in_issue_spec.rb +++ b/spec/features/projects/issues/design_management/user_links_to_designs_in_issue_spec.rb @@ -90,34 +90,5 @@ RSpec.describe 'viewing issues with design references' do expect(page).not_to have_link(design_ref_b) end end - - context 'design management is enabled, but the filter is disabled globally' do - before do - enable_design_management - stub_feature_flags( - Banzai::Filter::DesignReferenceFilter::FEATURE_FLAG => false - ) - end - - it 'processes design tab links successfully, and design references as issue references', :aggregate_failures do - visit_page_with_design_references - - expect(page).to have_text('The designs I mentioned') - expect(page).to have_link(design_tab_ref) - expect(page).to have_link(issue_ref) - expect(page).not_to have_link(design_ref_a) - expect(page).not_to have_link(design_ref_b) - end - end - - context 'design management is enabled, and the filter is enabled for the current project' do - before do - stub_feature_flags( - Banzai::Filter::DesignReferenceFilter::FEATURE_FLAG => public_project - ) - end - - it_behaves_like 'successful use of design link references' - end end end diff --git a/spec/features/projects/issues/viewing_relocated_issues_spec.rb b/spec/features/projects/issues/viewing_relocated_issues_spec.rb new file mode 100644 index 00000000000..10d5ad1747c --- /dev/null +++ b/spec/features/projects/issues/viewing_relocated_issues_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'issues canonical link' do + include Spec::Support::Helpers::Features::CanonicalLinkHelpers + + let_it_be(:original_project) { create(:project, :public) } + let_it_be(:original_issue) { create(:issue, project: original_project) } + let_it_be(:canonical_issue) { create(:issue) } + let_it_be(:canonical_url) { issue_url(canonical_issue, Gitlab::Application.routes.default_url_options) } + + it "doesn't show the canonical URL" do + visit(issue_path(original_issue)) + + expect(page).not_to have_any_canonical_links + end + + context 'when the issue was moved' do + it 'shows the canonical URL' do + original_issue.moved_to = canonical_issue + original_issue.save! + + visit(issue_path(original_issue)) + + expect(page).to have_canonical_link(canonical_url) + end + end + + context 'when the issue was duplicated' do + it 'shows the canonical URL' do + original_issue.duplicated_to = canonical_issue + original_issue.save! + + visit(issue_path(original_issue)) + + expect(page).to have_canonical_link(canonical_url) + end + end +end diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb index b935b99642b..9b199157d79 100644 --- a/spec/features/projects/jobs/user_browses_job_spec.rb +++ b/spec/features/projects/jobs/user_browses_job_spec.rb @@ -43,7 +43,7 @@ RSpec.describe 'User browses a job', :js do wait_for_all_requests within('.builds-container') do expect(page).to have_selector( - ".build-job > a[data-original-title='test - failed - (unknown failure)']") + ".build-job > a[title='test - failed - (unknown failure)']") end end end @@ -55,7 +55,7 @@ RSpec.describe 'User browses a job', :js do wait_for_all_requests within('.builds-container') do expect(page).to have_selector( - ".build-job > a[data-original-title='test - failed - (unknown failure) (retried)']") + ".build-job > a[title='test - failed - (unknown failure) (retried)']") end end end diff --git a/spec/features/projects/members/groups_with_access_list_spec.rb b/spec/features/projects/members/groups_with_access_list_spec.rb index 2ee6bc103e9..d59f8eb4b1d 100644 --- a/spec/features/projects/members/groups_with_access_list_spec.rb +++ b/spec/features/projects/members/groups_with_access_list_spec.rb @@ -3,20 +3,23 @@ require 'spec_helper' RSpec.describe 'Projects > Members > Groups with access list', :js do - let(:user) { create(:user) } - let(:group) { create(:group, :public) } - let(:project) { create(:project, :public) } + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group, :public) } + let_it_be(:project) { create(:project, :public) } + + let(:additional_link_attrs) { {} } + let!(:group_link) { create(:project_group_link, project: project, group: group, **additional_link_attrs) } before do - project.add_maintainer(user) - @group_link = create(:project_group_link, project: project, group: group) + travel_to Time.now.utc.beginning_of_day + project.add_maintainer(user) sign_in(user) visit project_project_members_path(project) end it 'updates group access level' do - click_button @group_link.human_access + click_button group_link.human_access page.within '.dropdown-menu' do click_link 'Guest' @@ -30,20 +33,38 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do end it 'updates expiry date' do - tomorrow = Date.today + 3 + expires_at_field = "member_expires_at_#{group.id}" + fill_in expires_at_field, with: 3.days.from_now.to_date - fill_in "member_expires_at_#{group.id}", with: tomorrow.strftime("%F") - find('body').click + find_field(expires_at_field).native.send_keys :enter wait_for_requests page.within(find('li.group_member')) do - expect(page).to have_content('Expires in') + expect(page).to have_content('Expires in 3 days') + end + end + + context 'when link has expiry date set' do + let(:additional_link_attrs) { { expires_at: 3.days.from_now.to_date } } + + it 'clears expiry date' do + page.within(find('li.group_member')) do + expect(page).to have_content('Expires in 3 days') + + page.within(find('.js-edit-member-form')) do + find('.js-clear-input').click + end + + wait_for_requests + + expect(page).not_to have_content('Expires in') + end end end it 'deletes group link' do page.within(first('.group_member')) do - accept_confirm { find('.btn-remove').click } + accept_confirm { find('.btn-danger').click } end wait_for_requests diff --git a/spec/features/projects/members/list_spec.rb b/spec/features/projects/members/list_spec.rb index b32ccb0ccef..36ff461aac2 100644 --- a/spec/features/projects/members/list_spec.rb +++ b/spec/features/projects/members/list_spec.rb @@ -102,7 +102,7 @@ RSpec.describe 'Project members list' do visit_members_page expect(page).not_to have_selector("#edit_project_member_#{project_member.id}") - expect(page).not_to have_selector("#project_member_#{project_member.id} .btn-remove") + expect(page).to have_no_selector("#project_member_#{project_member.id} .btn-danger") end 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 979bbd57aa3..d69c3f2652c 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 @@ -6,43 +6,64 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date include Select2Helper include ActiveSupport::Testing::TimeHelpers - let(:maintainer) { create(:user) } - let(:project) { create(:project) } - let!(:new_member) { create(:user) } + let_it_be(:maintainer) { create(:user) } + let_it_be(:project) { create(:project) } + let(:new_member) { create(:user) } before do + travel_to Time.now.utc.beginning_of_day + project.add_maintainer(maintainer) sign_in(maintainer) end it 'expiration date is displayed in the members list' do - travel_to Time.zone.parse('2016-08-06 08:00') do - date = 4.days.from_now - 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: date.to_s(:medium) + "\n" - click_on 'Invite' - end - - page.within "#project_member_#{new_member.project_members.first.id}" do - expect(page).to have_content('Expires in 4 days') - end + 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: 3.days.from_now.to_date + find_field('expires_at').native.send_keys :enter + + click_on 'Invite' + end + + page.within "#project_member_#{project_member_id}" do + expect(page).to have_content('Expires in 3 days') + end + end + + it 'changes expiration date' do + project.team.add_users([new_member.id], :developer, expires_at: Date.today.to_date) + visit project_project_members_path(project) + + page.within "#project_member_#{project_member_id}" do + fill_in 'Expiration date', with: 3.days.from_now.to_date + find_field('Expiration date').native.send_keys :enter + + wait_for_requests + + expect(page).to have_content('Expires in 3 days') end end - it 'change expiration date' do - travel_to Time.zone.parse('2016-08-06 08:00') do - date = 3.days.from_now - project.team.add_users([new_member.id], :developer, expires_at: Date.today.to_s(:medium)) - visit project_project_members_path(project) - - page.within "#project_member_#{new_member.project_members.first.id}" do - find('.js-access-expiration-date').set date.to_s(:medium) + "\n" - wait_for_requests - expect(page).to have_content('Expires in 3 days') - end + it 'clears expiration date' do + project.team.add_users([new_member.id], :developer, expires_at: 3.days.from_now.to_date) + visit project_project_members_path(project) + + page.within "#project_member_#{project_member_id}" do + expect(page).to have_content('Expires in 3 days') + + find('.js-clear-input').click + + wait_for_requests + + expect(page).not_to have_content('Expires in') end end + + def project_member_id + project.members.find_by(user_id: new_member).id + end end diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb index 07f65fe62df..4ff3827b240 100644 --- a/spec/features/projects/navbar_spec.rb +++ b/spec/features/projects/navbar_spec.rb @@ -12,20 +12,10 @@ RSpec.describe 'Project navbar' do let_it_be(:project) { create(:project, :repository) } before do - stub_feature_flags(project_iterations: false) - insert_package_nav(_('Operations')) project.add_maintainer(user) sign_in(user) - - if Gitlab.ee? - insert_after_sub_nav_item( - _('Kubernetes'), - within: _('Operations'), - new_sub_nav_item_name: _('Feature Flags') - ) - end end it_behaves_like 'verified navigation bar' do diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb index 243579ee2f7..c3eea0195a6 100644 --- a/spec/features/projects/pages_spec.rb +++ b/spec/features/projects/pages_spec.rb @@ -336,7 +336,7 @@ RSpec.shared_examples 'pages settings editing' do expect(page).not_to have_field(:project_pages_https_only) expect(page).not_to have_content('Force HTTPS (requires valid certificates)') - expect(page).not_to have_button('Save') + expect(page).to have_button('Save') end end end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index f59dc5dd074..51826d867cd 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -172,10 +172,17 @@ RSpec.describe 'Pipeline', :js do end end - it_behaves_like 'showing user status' do - let(:user_with_status) { pipeline.user } + describe 'pipelines details view' do + let!(:status) { create(:user_status, user: pipeline.user, emoji: 'smirk', message: 'Authoring this object') } - subject { visit project_pipeline_path(project, pipeline) } + it 'pipeline header shows the user status and emoji' do + visit project_pipeline_path(project, pipeline) + + within '[data-testid="ci-header-content"]' do + expect(page).to have_selector("[data-testid='#{status.message}']") + expect(page).to have_selector("[data-name='#{status.emoji}']") + end + end end describe 'pipeline graph' do @@ -400,7 +407,7 @@ RSpec.describe 'Pipeline', :js do context 'when retrying' do before do - find('[data-testid="retryButton"]').click + find('[data-testid="retryPipeline"]').click end it 'does not show a "Retry" button', :sidekiq_might_not_need_inline do @@ -902,7 +909,7 @@ RSpec.describe 'Pipeline', :js do context 'when retrying' do before do - find('[data-testid="retryButton"]').click + find('[data-testid="retryPipeline"]').click end it 'does not show a "Retry" button', :sidekiq_might_not_need_inline do diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index a9c196bb84b..3e78dfc3bc7 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -118,7 +118,7 @@ RSpec.describe 'Pipelines', :js do context 'when canceling' do before do find('.js-pipelines-cancel-button').click - find('.js-modal-primary-action').click + click_button 'Stop pipeline' wait_for_requests end @@ -407,7 +407,7 @@ RSpec.describe 'Pipelines', :js do context 'when canceling' do before do find('.js-pipelines-cancel-button').click - find('.js-modal-primary-action').click + click_button 'Stop pipeline' end it 'indicates that pipeline was canceled', :sidekiq_might_not_need_inline do diff --git a/spec/features/projects/releases/user_creates_release_spec.rb b/spec/features/projects/releases/user_creates_release_spec.rb index 5d05a7e4c91..0a5f7cc7edd 100644 --- a/spec/features/projects/releases/user_creates_release_spec.rb +++ b/spec/features/projects/releases/user_creates_release_spec.rb @@ -11,14 +11,11 @@ RSpec.describe 'User creates release', :js do let_it_be(:user) { create(:user) } let(:new_page_url) { new_project_release_path(project) } - let(:show_feature_flag) { true } before do - stub_feature_flags(release_show_page: show_feature_flag) - project.add_developer(user) - gitlab_sign_in(user) + sign_in(user) visit new_page_url @@ -75,14 +72,6 @@ RSpec.describe 'User creates release', :js do expect(page).to have_current_path(project_release_path(project, release)) end - - context 'when the release_show_page feature flag is disabled' do - let(:show_feature_flag) { false } - - it 'redirects to the main "Releases" page' do - expect(page).to have_current_path(project_releases_path(project)) - end - end end context 'when the "Cancel" button is clicked' do @@ -108,6 +97,24 @@ RSpec.describe 'User creates release', :js do end end + context 'when the release notes "Preview" tab is clicked' do + before do + find_field('Release notes').click + + fill_release_notes('**some** _markdown_ [content](https://example.com)') + + click_on 'Preview' + + wait_for_all_requests + end + + it 'renders a preview of the release notes markdown' do + within('[data-testid="release-notes"]') do + expect(page).to have_text('some markdown content') + end + end + end + def fill_out_form_and_submit fill_tag_name(tag_name) diff --git a/spec/features/projects/releases/user_views_edit_release_spec.rb b/spec/features/projects/releases/user_views_edit_release_spec.rb index 4ed1be6db6b..9115a135aeb 100644 --- a/spec/features/projects/releases/user_views_edit_release_spec.rb +++ b/spec/features/projects/releases/user_views_edit_release_spec.rb @@ -6,14 +6,11 @@ RSpec.describe 'User edits Release', :js do let_it_be(:project) { create(:project, :repository) } let_it_be(:release) { create(:release, project: project, name: 'The first release' ) } let_it_be(:user) { create(:user) } - let(:show_feature_flag) { true } before do - stub_feature_flags(release_show_page: show_feature_flag) - project.add_developer(user) - gitlab_sign_in(user) + sign_in(user) visit edit_project_release_path(project, release) @@ -42,7 +39,7 @@ RSpec.describe 'User edits Release', :js do it 'renders the edit Release form' do expect(page).to have_content('Releases are based on Git tags. We recommend tags that use semantic versioning, for example v1.0, v2.0-pre.') - expect(find_field('Tag name', { disabled: true }).value).to eq(release.tag) + expect(find_field('Tag name', disabled: true).value).to eq(release.tag) expect(find_field('Release title').value).to eq(release.name) expect(find_field('Release notes').value).to eq(release.description) @@ -71,42 +68,24 @@ RSpec.describe 'User edits Release', :js do expect(release.description).to eq('Updated Release notes') end - context 'when the release_show_page feature flag is disabled' do - let(:show_feature_flag) { false } - - it 'redirects to the main Releases page when "Cancel" is clicked' do - fill_out_form_and_click 'Cancel' - - expect(page).to have_current_path(project_releases_path(project)) - end + it 'redirects to the previous page when "Cancel" is clicked when the url includes a back_url query parameter' do + back_path = project_releases_path(project, params: { page: 2 }) + visit edit_project_release_path(project, release, params: { back_url: back_path }) - it 'redirects to the main Releases page when "Save changes" is clicked' do - fill_out_form_and_click 'Save changes' + fill_out_form_and_click 'Cancel' - expect(page).to have_current_path(project_releases_path(project)) - end + expect(page).to have_current_path(back_path) end - context 'when the release_show_page feature flag is enabled' do - it 'redirects to the previous page when "Cancel" is clicked when the url includes a back_url query parameter' do - back_path = project_releases_path(project, params: { page: 2 }) - visit edit_project_release_path(project, release, params: { back_url: back_path }) - - fill_out_form_and_click 'Cancel' - - expect(page).to have_current_path(back_path) - end - - it 'redirects to the main Releases page when "Cancel" is clicked when the url does not include a back_url query parameter' do - fill_out_form_and_click 'Cancel' + it 'redirects to the main Releases page when "Cancel" is clicked when the url does not include a back_url query parameter' do + fill_out_form_and_click 'Cancel' - expect(page).to have_current_path(project_releases_path(project)) - end + expect(page).to have_current_path(project_releases_path(project)) + end - it 'redirects to the dedicated Release page when "Save changes" is clicked' do - fill_out_form_and_click 'Save changes' + it 'redirects to the dedicated Release page when "Save changes" is clicked' do + fill_out_form_and_click 'Save changes' - expect(page).to have_current_path(project_release_path(project, release)) - end + expect(page).to have_current_path(project_release_path(project, release)) end end diff --git a/spec/features/projects/releases/user_views_release_spec.rb b/spec/features/projects/releases/user_views_release_spec.rb index c82588746a8..186122536ce 100644 --- a/spec/features/projects/releases/user_views_release_spec.rb +++ b/spec/features/projects/releases/user_views_release_spec.rb @@ -4,34 +4,57 @@ require 'spec_helper' RSpec.describe 'User views Release', :js do let(:project) { create(:project, :repository) } - let(:release) { create(:release, project: project, name: 'The first release' ) } let(:user) { create(:user) } + let(:graphql_feature_flag) { true } + + let(:release) do + create(:release, + project: project, + name: 'The first release', + description: '**Lorem** _ipsum_ dolor sit [amet](https://example.com)') + end before do + stub_feature_flags(graphql_individual_release_page: graphql_feature_flag) + project.add_developer(user) - gitlab_sign_in(user) + sign_in(user) visit project_release_path(project, release) end - it 'renders the breadcrumbs' do - within('.breadcrumbs') do - expect(page).to have_content("#{project.creator.name} #{project.name} Releases #{release.name}") + it_behaves_like 'page meta description', 'Lorem ipsum dolor sit amet' - expect(page).to have_link(project.creator.name, href: user_path(project.creator)) - expect(page).to have_link(project.name, href: project_path(project)) - expect(page).to have_link('Releases', href: project_releases_path(project)) - expect(page).to have_link(release.name, href: project_release_path(project, release)) + shared_examples 'release page' do + it 'renders the breadcrumbs' do + within('.breadcrumbs') do + expect(page).to have_content("#{project.creator.name} #{project.name} Releases #{release.name}") + + expect(page).to have_link(project.creator.name, href: user_path(project.creator)) + expect(page).to have_link(project.name, href: project_path(project)) + expect(page).to have_link('Releases', href: project_releases_path(project)) + expect(page).to have_link(release.name, href: project_release_path(project, release)) + end end - end - it 'renders the release details' do - within('.release-block') do - expect(page).to have_content(release.name) - expect(page).to have_content(release.tag) - expect(page).to have_content(release.commit.short_id) - expect(page).to have_content(release.description) + it 'renders the release details' do + within('.release-block') do + expect(page).to have_content(release.name) + expect(page).to have_content(release.tag) + expect(page).to have_content(release.commit.short_id) + expect(page).to have_content('Lorem ipsum dolor sit amet') + end end end + + describe 'when the graphql_individual_release_page feature flag is enabled' do + it_behaves_like 'release page' + end + + describe 'when the graphql_individual_release_page feature flag is disabled' do + let(:graphql_feature_flag) { false } + + it_behaves_like 'release page' + end end diff --git a/spec/features/projects/releases/user_views_releases_spec.rb b/spec/features/projects/releases/user_views_releases_spec.rb index 993d3371904..323c57570c3 100644 --- a/spec/features/projects/releases/user_views_releases_spec.rb +++ b/spec/features/projects/releases/user_views_releases_spec.rb @@ -16,7 +16,7 @@ RSpec.describe 'User views releases', :js do shared_examples 'releases page' do context('when the user is a maintainer') do before do - gitlab_sign_in(maintainer) + sign_in(maintainer) end it 'sees the release' do @@ -27,11 +27,23 @@ RSpec.describe 'User views releases', :js do expect(page).not_to have_content('Upcoming Release') end - shared_examples 'asset link tests' do - context 'when there is a link as an asset' do - let!(:release_link) { create(:release_link, release: release, url: url ) } + context 'when there is a link as an asset' do + let!(:release_link) { create(:release_link, release: release, url: url ) } + let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" } + let(:direct_asset_link) { Gitlab::Routing.url_helpers.project_release_url(project, release) << release_link.filepath } + + it 'sees the link' do + visit project_releases_path(project) + + page.within('.js-assets-list') do + expect(page).to have_link release_link.name, href: direct_asset_link + expect(page).not_to have_css('[data-testid="external-link-indicator"]') + end + end + + context 'when there is a link redirect' do + let!(:release_link) { create(:release_link, release: release, name: 'linux-amd64 binaries', filepath: '/binaries/linux-amd64', url: url) } let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" } - let(:direct_asset_link) { Gitlab::Routing.url_helpers.project_release_url(project, release) << release_link.filepath } it 'sees the link' do visit project_releases_path(project) @@ -41,51 +53,21 @@ RSpec.describe 'User views releases', :js do 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, name: 'linux-amd64 binaries', filepath: '/binaries/linux-amd64', url: url) } - let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" } - - it 'sees the link' do - visit project_releases_path(project) - - page.within('.js-assets-list') do - 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 - visit project_releases_path(project) + it 'sees that the link is external resource' do + visit project_releases_path(project) - page.within('.js-assets-list') do - expect(page).to have_css('[data-testid="external-link-indicator"]') - end + page.within('.js-assets-list') do + expect(page).to have_css('[data-testid="external-link-indicator"]') end end end end - context 'when the release_asset_link_type feature flag is enabled' do - before do - stub_feature_flags(release_asset_link_type: true) - end - - it_behaves_like 'asset link tests' - end - - context 'when the release_asset_link_type feature flag is disabled' do - before do - stub_feature_flags(release_asset_link_type: false) - end - - it_behaves_like 'asset link tests' - end - context 'with an upcoming release' do let(:tomorrow) { Time.zone.now + 1.day } let!(:release) { create(:release, project: project, released_at: tomorrow ) } @@ -110,7 +92,7 @@ RSpec.describe 'User views releases', :js do context('when the user is a guest') do before do - gitlab_sign_in(guest) + sign_in(guest) end it 'renders release info except for Git-related data' do diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb index 0358acc8dcc..ffc0ecc4966 100644 --- a/spec/features/projects/settings/pipelines_settings_spec.rb +++ b/spec/features/projects/settings/pipelines_settings_spec.rb @@ -64,7 +64,7 @@ RSpec.describe "Projects > Settings > Pipelines settings" do it 'updates forward_deployment_enabled' do visit project_settings_ci_cd_path(project) - checkbox = find_field('project_forward_deployment_enabled') + checkbox = find_field('project_ci_cd_settings_attributes_forward_deployment_enabled') expect(checkbox).to be_checked checkbox.set(false) @@ -79,7 +79,7 @@ RSpec.describe "Projects > Settings > Pipelines settings" do expect(page).to have_button('Save changes', disabled: false) end - checkbox = find_field('project_forward_deployment_enabled') + checkbox = find_field('project_ci_cd_settings_attributes_forward_deployment_enabled') expect(checkbox).not_to be_checked end diff --git a/spec/features/projects/settings/registry_settings_spec.rb b/spec/features/projects/settings/registry_settings_spec.rb index 8e2f97fd6a0..4e1b53ffc87 100644 --- a/spec/features/projects/settings/registry_settings_spec.rb +++ b/spec/features/projects/settings/registry_settings_spec.rb @@ -3,27 +3,35 @@ require 'spec_helper' RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration policy', :js do - let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace, container_registry_enabled: container_registry_enabled) } + using RSpec::Parameterized::TableSyntax + + let_it_be(:user) { create(:user) } + let_it_be(:project, reload: true) { create(:project, namespace: user.namespace) } + let(:container_registry_enabled) { true } + let(:container_registry_enabled_on_project) { true } + + subject { visit project_settings_ci_cd_path(project) } before do + project.update!(container_registry_enabled: container_registry_enabled_on_project) + sign_in(user) - stub_container_registry_config(enabled: true) + stub_container_registry_config(enabled: container_registry_enabled) stub_feature_flags(new_variables_ui: false) end context 'as owner' do - before do - visit project_settings_ci_cd_path(project) - end - it 'shows available section' do + subject + settings_block = find('#js-registry-policies') expect(settings_block).to have_text 'Cleanup policy for tags' end it 'saves cleanup policy submit the form' do + subject + within '#js-registry-policies' do within '.card-body' do select('7 days until tags are automatically removed', from: 'Expiration interval:') @@ -40,6 +48,8 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p end it 'does not save cleanup policy submit form with invalid regex' do + subject + within '#js-registry-policies' do within '.card-body' do fill_in('Tags with names matching this regex pattern will expire:', with: '*-production') @@ -53,25 +63,53 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p end end - context 'when registry is disabled' do - before do - stub_container_registry_config(enabled: false) - visit project_settings_ci_cd_path(project) + 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 - it 'does not exists' do - expect(page).not_to have_selector('#js-registry-policies') + 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 + + it 'displays the expected result' do + subject + + within '#js-registry-policies' do + case result + when :available_section + expect(find('.card-header')).to have_content('Tag expiration policy') + when :disabled_message + expect(find('.gl-alert-title')).to have_content('Cleanup policy for tags is disabled') + end + end + end end end - context 'when container registry is disabled on project' do + context 'when registry is disabled' do let(:container_registry_enabled) { false } - before do - visit project_settings_ci_cd_path(project) + it 'does not exists' do + subject + + expect(page).not_to have_selector('#js-registry-policies') end + end + + context 'when container registry is disabled on project' do + let(:container_registry_enabled_on_project) { false } it 'does not exists' do + subject + expect(page).not_to have_selector('#js-registry-policies') end end diff --git a/spec/features/projects/show/user_manages_notifications_spec.rb b/spec/features/projects/show/user_manages_notifications_spec.rb index 9d9a75c22be..d444ea27d35 100644 --- a/spec/features/projects/show/user_manages_notifications_spec.rb +++ b/spec/features/projects/show/user_manages_notifications_spec.rb @@ -18,7 +18,9 @@ RSpec.describe 'Projects > Show > User manages notifications', :js do click_notifications_button click_link 'On mention' - wait_for_requests + page.within('.notification-dropdown') do + expect(page).not_to have_css('.gl-spinner') + end click_notifications_button expect(find('.update-notification.is-active')).to have_content('On mention') @@ -30,7 +32,9 @@ RSpec.describe 'Projects > Show > User manages notifications', :js do click_notifications_button click_link 'Disabled' - wait_for_requests + page.within('.notification-dropdown') do + expect(page).not_to have_css('.gl-spinner') + end expect(page).to have_css('.notifications-icon[data-testid="notifications-off-icon"]') end diff --git a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb index 81736fefae9..189aa45ff75 100644 --- a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb +++ b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb @@ -46,21 +46,21 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do visit project_path(project) end - it '"New file" button linked to new file page' do + it '"New file" button linked to IDE new file page' do page.within('.project-buttons') do - expect(page).to have_link('New file', href: project_new_blob_path(project, project.default_branch || 'master')) + expect(page).to have_link('New file', href: presenter.ide_edit_path(project, project.default_branch || 'master')) end end - it '"Add README" button linked to new file populated for a README' do + it '"Add README" button linked to IDE new file populated for a README' do page.within('.project-buttons') do - expect(page).to have_link('Add README', href: presenter.add_readme_path) + expect(page).to have_link('Add README', href: presenter.add_readme_ide_path) end end - it '"Add license" button linked to new file populated for a license' do + it '"Add license" button linked to IDE new file populated for a license' do page.within('.project-buttons') do - expect(page).to have_link('Add LICENSE', href: presenter.add_license_path) + expect(page).to have_link('Add LICENSE', href: presenter.add_license_ide_path) end end @@ -74,9 +74,9 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do visit project_path(project) end - it '"New file" button linked to new file page' do + it '"New file" button linked to IDE new file page' do page.within('.project-buttons') do - expect(page).to have_link('New file', href: project_new_blob_path(project, 'example_branch')) + expect(page).to have_link('New file', href: presenter.ide_edit_path(project, 'example_branch')) end end end @@ -144,7 +144,7 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do expect(project.repository.readme).not_to be_nil page.within('.project-buttons') do - expect(page).not_to have_link('Add README', href: presenter.add_readme_path) + expect(page).not_to have_link('Add README', href: presenter.add_readme_ide_path) expect(page).to have_link('README', href: presenter.readme_path) end end @@ -164,7 +164,7 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do end context 'when the project does not have a README' do - it 'shows the "Add README" button' do + it 'shows the single file editor "Add README" button' do allow(project.repository).to receive(:readme).and_return(nil) visit project_path(project) @@ -226,7 +226,7 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do expect(project.repository.gitlab_ci_yml).to be_nil page.within('.project-buttons') do - expect(page).to have_link('Set up CI/CD', href: presenter.add_ci_yml_ide_path) + expect(page).to have_link('Set up CI/CD', href: presenter.add_ci_yml_path) end end diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb index 503246bbdcf..28fe0a0b7e1 100644 --- a/spec/features/projects/snippets/create_snippet_spec.rb +++ b/spec/features/projects/snippets/create_snippet_spec.rb @@ -17,115 +17,81 @@ RSpec.describe 'Projects > Snippets > Create Snippet', :js do let(:file_content) { 'Hello World!' } let(:md_description) { 'My Snippet **Description**' } let(:description) { 'My Snippet Description' } - let(:snippet_title_field) { 'project_snippet_title' } - shared_examples 'snippet creation' do - def fill_form - snippet_fill_in_form(title: title, content: file_content, description: md_description) - end - - it 'shows collapsible description input' do - collapsed = description_field + def fill_form + snippet_fill_in_form(title: title, content: file_content, description: md_description) + end - expect(page).not_to have_field(snippet_description_field) - expect(collapsed).to be_visible + before do + sign_in(user) - collapsed.click + visit new_project_snippet_path(project) + end - expect(page).to have_field(snippet_description_field) - expect(collapsed).not_to be_visible - end + it 'shows collapsible description input' do + collapsed = snippet_description_field_collapsed - it 'creates a new snippet' do - fill_form - click_button('Create snippet') - wait_for_requests + expect(page).not_to have_field(snippet_description_locator) + expect(collapsed).to be_visible - expect(page).to have_content(title) - expect(page).to have_content(file_content) - page.within(snippet_description_view_selector) do - expect(page).to have_content(description) - expect(page).to have_selector('strong') - end - end + collapsed.click - it 'uploads a file when dragging into textarea' do - fill_form - dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') - - expect(snippet_description_value).to have_content('banana_sample') + expect(page).to have_field(snippet_description_locator) + expect(collapsed).not_to be_visible + end - click_button('Create snippet') - wait_for_requests + it 'creates a new snippet' do + fill_form + click_button('Create snippet') + wait_for_requests - link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] - expect(link).to match(%r{/#{Regexp.escape(project.full_path)}/uploads/\h{32}/banana_sample\.gif\z}) + expect(page).to have_content(title) + expect(page).to have_content(file_content) + page.within('.snippet-header .snippet-description') do + expect(page).to have_content(description) + expect(page).to have_selector('strong') end + end - context 'when the git operation fails' do - let(:error) { 'Error creating the snippet' } - - before do - allow_next_instance_of(Snippets::CreateService) do |instance| - allow(instance).to receive(:create_commit).and_raise(StandardError, error) - end + it 'uploads a file when dragging into textarea' do + fill_form + dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') - fill_form + expect(snippet_description_value).to have_content('banana_sample') - click_button('Create snippet') - wait_for_requests - end + click_button('Create snippet') + wait_for_requests - it 'renders the new page and displays the error' do - expect(page).to have_content(error) - expect(page).to have_content('New Snippet') - end - end + link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] + expect(link).to match(%r{/#{Regexp.escape(project.full_path)}/uploads/\h{32}/banana_sample\.gif\z}) end - context 'Vue application' do - let(:snippet_description_field) { 'snippet-description' } - let(:snippet_description_view_selector) { '.snippet-header .snippet-description' } + context 'when the git operation fails' do + let(:error) { 'Error creating the snippet' } before do - sign_in(user) - - visit new_project_snippet_path(project) - end - - it_behaves_like 'snippet creation' - - it 'does not allow submitting the form without title and content' do - fill_in snippet_title_field, with: title + allow_next_instance_of(Snippets::CreateService) do |instance| + allow(instance).to receive(:create_commit).and_raise(StandardError, error) + end - expect(page).not_to have_button('Create snippet') + fill_form - snippet_fill_in_form(title: title, content: file_content) - expect(page).to have_button('Create snippet') + click_button('Create snippet') + wait_for_requests end - end - - context 'non-Vue application' do - let(:snippet_description_field) { 'project_snippet_description' } - let(:snippet_description_view_selector) { '.snippet-header .description' } - - before do - stub_feature_flags(snippets_vue: false) - stub_feature_flags(snippets_edit_vue: false) - - sign_in(user) - visit new_project_snippet_path(project) + it 'renders the new page and displays the error' do + expect(page).to have_content(error) + expect(page).to have_content('New Snippet') end + end - it_behaves_like 'snippet creation' + it 'does not allow submitting the form without title and content' do + snippet_fill_in_title(title) - it 'displays validation errors' do - fill_in snippet_title_field, with: title - click_button('Create snippet') - wait_for_requests + expect(page).not_to have_button('Create snippet') - expect(page).to have_selector('#error_explanation') - end + snippet_fill_in_form(title: title, content: file_content) + expect(page).to have_button('Create snippet') end end diff --git a/spec/features/projects/snippets/show_spec.rb b/spec/features/projects/snippets/show_spec.rb index 8fded3cde80..5937ff75457 100644 --- a/spec/features/projects/snippets/show_spec.rb +++ b/spec/features/projects/snippets/show_spec.rb @@ -13,8 +13,6 @@ RSpec.describe 'Projects > Snippets > Project snippet', :js do let_it_be(:snippet) { create(:project_snippet, :repository, project: project, author: user) } before do - stub_feature_flags(snippets_vue: false) - sign_in(user) end @@ -28,12 +26,8 @@ RSpec.describe 'Projects > Snippets > Project snippet', :js do end end - it_behaves_like 'showing user status' do - let(:file_path) { 'files/ruby/popen.rb' } - let(:user_with_status) { snippet.author } - - subject { visit project_snippet_path(project, snippet) } - end + # it_behaves_like 'showing user status' do + # This will be handled in https://gitlab.com/gitlab-org/gitlab/-/issues/262394 it_behaves_like 'does not show New Snippet button' do let(:file_path) { 'files/ruby/popen.rb' } diff --git a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb index 2784fec3dc1..b37d40c0eed 100644 --- a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb +++ b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb @@ -8,7 +8,6 @@ RSpec.describe 'Projects > Snippets > User comments on a snippet', :js do let_it_be(:snippet) { create(:project_snippet, :repository, project: project, author: user) } before do - stub_feature_flags(snippets_vue: false) project.add_maintainer(user) sign_in(user) diff --git a/spec/features/projects/snippets/user_deletes_snippet_spec.rb b/spec/features/projects/snippets/user_deletes_snippet_spec.rb index 44fe9834484..6d526e60512 100644 --- a/spec/features/projects/snippets/user_deletes_snippet_spec.rb +++ b/spec/features/projects/snippets/user_deletes_snippet_spec.rb @@ -2,13 +2,12 @@ require 'spec_helper' -RSpec.describe 'Projects > Snippets > User deletes a snippet' do +RSpec.describe 'Projects > Snippets > User deletes a snippet', :js do let(:project) { create(:project) } - let!(:snippet) { create(:project_snippet, project: project, author: user) } + let!(:snippet) { create(:project_snippet, :repository, project: project, author: user) } let(:user) { create(:user) } before do - stub_feature_flags(snippets_vue: false) project.add_maintainer(user) sign_in(user) @@ -16,7 +15,11 @@ RSpec.describe 'Projects > Snippets > User deletes a snippet' do end it 'deletes a snippet' do - first(:link, 'Delete').click + expect(page).to have_content(snippet.title) + + click_button('Delete') + click_button('Delete snippet') + wait_for_requests expect(page).not_to have_content(snippet.title) end diff --git a/spec/features/projects/snippets/user_updates_snippet_spec.rb b/spec/features/projects/snippets/user_updates_snippet_spec.rb index 193eaa9576a..aa498163f52 100644 --- a/spec/features/projects/snippets/user_updates_snippet_spec.rb +++ b/spec/features/projects/snippets/user_updates_snippet_spec.rb @@ -9,9 +9,7 @@ RSpec.describe 'Projects > Snippets > User updates a snippet', :js do let_it_be(:project) { create(:project, namespace: user.namespace) } let_it_be(:snippet, reload: true) { create(:project_snippet, :repository, project: project, author: user) } - let(:snippet_title_field) { 'project_snippet_title' } - - def bootstrap_snippet + before do project.add_maintainer(user) sign_in(user) @@ -20,64 +18,36 @@ RSpec.describe 'Projects > Snippets > User updates a snippet', :js do wait_for_all_requests end - shared_examples 'snippet update' do - it 'displays the snippet blob path and content' do - blob = snippet.blobs.first - - aggregate_failures do - expect(snippet_get_first_blob_path).to eq blob.path - expect(snippet_get_first_blob_value).to have_content(blob.data.strip) - end - end - - it 'updates a snippet' do - fill_in('project_snippet_title', with: 'Snippet new title') - click_button('Save') + it 'displays the snippet blob path and content' do + blob = snippet.blobs.first - expect(page).to have_content('Snippet new title') - end - - context 'when the git operation fails' do - before do - allow_next_instance_of(Snippets::UpdateService) do |instance| - allow(instance).to receive(:create_commit).and_raise(StandardError, 'Error Message') - end - - fill_in(snippet_title_field, with: 'Snippet new title') - fill_in(snippet_blob_path_field, match: :first, with: 'new_file_name') - - click_button('Save') - end - - it 'renders edit page and displays the error' do - expect(page.find('.flash-container')).to have_content('Error updating the snippet - Error Message') - expect(page).to have_content('Edit Snippet') - end + aggregate_failures do + expect(snippet_get_first_blob_path).to eq blob.path + expect(snippet_get_first_blob_value).to have_content(blob.data.strip) end end - context 'Vue application' do - before do - bootstrap_snippet - end + it 'updates a snippet' do + fill_in('snippet-title', with: 'Snippet new title') + click_button('Save') - it_behaves_like 'snippet update' do - let(:snippet_blob_path_field) { 'snippet_file_name' } - let(:snippet_blob_content_selector) { '.file-content' } - end + expect(page).to have_content('Snippet new title') end - context 'non-Vue application' do + context 'when the git operation fails' do before do - stub_feature_flags(snippets_vue: false) - stub_feature_flags(snippets_edit_vue: false) + allow_next_instance_of(Snippets::UpdateService) do |instance| + allow(instance).to receive(:create_commit).and_raise(StandardError, 'Error Message') + end - bootstrap_snippet + snippet_fill_in_form(title: 'Snippet new title', file_name: 'new_file_name') + + click_button('Save') end - it_behaves_like 'snippet update' do - let(:snippet_blob_path_field) { 'project_snippet_file_name' } - let(:snippet_blob_content_selector) { '.file-content' } + it 'renders edit page and displays the error' do + expect(page.find('.flash-container')).to have_content('Error updating the snippet - Error Message') + expect(page).to have_content('Edit Snippet') end end end diff --git a/spec/features/projects/tracings_spec.rb b/spec/features/projects/tracings_spec.rb new file mode 100644 index 00000000000..c4a4f1382ed --- /dev/null +++ b/spec/features/projects/tracings_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Tracings Content Security Policy' do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + + subject { response_headers['Content-Security-Policy'] } + + before_all do + project.add_maintainer(user) + end + + before do + sign_in(user) + end + + context 'when there is no global config' do + before do + expect_next_instance_of(Projects::TracingsController) do |controller| + expect(controller).to receive(:current_content_security_policy) + .and_return(ActionDispatch::ContentSecurityPolicy.new) + end + end + + it 'does not add CSP directives' do + visit project_tracing_path(project) + + is_expected.to be_blank + end + end + + context 'when a global CSP config exists' do + before do + csp = ActionDispatch::ContentSecurityPolicy.new do |p| + p.frame_src 'https://global-policy.com' + end + + expect_next_instance_of(Projects::TracingsController) do |controller| + expect(controller).to receive(:current_content_security_policy).and_return(csp) + end + end + + context 'when external_url is set' do + let!(:project_tracing_setting) { create(:project_tracing_setting, project: project) } + + it 'overwrites frame-src' do + visit project_tracing_path(project) + + is_expected.to eq("frame-src https://example.com") + end + end + + context 'when external_url is not set' do + it 'uses global policy' do + visit project_tracing_path(project) + + is_expected.to eq("frame-src https://global-policy.com") + end + end + end +end diff --git a/spec/features/projects/tree/tree_show_spec.rb b/spec/features/projects/tree/tree_show_spec.rb index bd2af66710a..ca9e0a23888 100644 --- a/spec/features/projects/tree/tree_show_spec.rb +++ b/spec/features/projects/tree/tree_show_spec.rb @@ -69,7 +69,7 @@ RSpec.describe 'Projects tree', :js do # Check last commit expect(find('.commit-content').text).to include(message) - expect(find('.commit-sha-group').text).to eq(short_newrev) + expect(find('.js-commit-sha-group').text).to eq(short_newrev) end end diff --git a/spec/features/projects/user_sees_sidebar_spec.rb b/spec/features/projects/user_sees_sidebar_spec.rb index 50d7b353c46..616c5065c07 100644 --- a/spec/features/projects/user_sees_sidebar_spec.rb +++ b/spec/features/projects/user_sees_sidebar_spec.rb @@ -128,6 +128,59 @@ RSpec.describe 'Projects > User sees sidebar' do end end + context 'as anonymous' do + let(:project) { create(:project, :public) } + let!(:issue) { create(:issue, :opened, project: project, author: user) } + + describe 'project landing page' do + before do + project.project_feature.update!( + builds_access_level: ProjectFeature::DISABLED, + merge_requests_access_level: ProjectFeature::DISABLED, + repository_access_level: ProjectFeature::DISABLED, + issues_access_level: ProjectFeature::DISABLED, + wiki_access_level: ProjectFeature::DISABLED + ) + end + + it 'does not show the project file list landing page, but the activity' do + visit project_path(project) + + expect(page).not_to have_selector '.project-stats' + expect(page).not_to have_selector '.project-last-commit' + expect(page).not_to have_selector '.project-show-files' + expect(page).to have_selector '.project-show-activity' + end + + it 'shows the wiki when enabled' do + project.project_feature.update!(wiki_access_level: ProjectFeature::ENABLED) + + visit project_path(project) + + expect(page).to have_selector '.project-show-wiki' + end + + it 'shows the issues when enabled' do + project.project_feature.update!(issues_access_level: ProjectFeature::ENABLED) + + visit project_path(project) + + expect(page).to have_selector '.issues-list' + end + + it 'shows the wiki when wiki and issues are enabled' do + project.project_feature.update!( + issues_access_level: ProjectFeature::ENABLED, + wiki_access_level: ProjectFeature::ENABLED + ) + + visit project_path(project) + + expect(page).to have_selector '.project-show-wiki' + end + end + end + context 'as guest' do let(:guest) { create(:user) } let!(:issue) { create(:issue, :opened, project: project, author: guest) } @@ -145,11 +198,11 @@ 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).not_to have_content 'Repository' expect(page).not_to have_content 'CI / CD' expect(page).not_to have_content 'Merge Requests' - expect(page).not_to have_content 'Operations' end end @@ -194,13 +247,13 @@ RSpec.describe 'Projects > User sees sidebar' do expect(page).not_to have_selector '.project-stats' expect(page).not_to have_selector '.project-last-commit' expect(page).not_to have_selector '.project-show-files' - expect(page).to have_selector '.project-show-customize_workflow' + expect(page).to have_selector '.project-show-activity' end - it 'shows the customize workflow when issues and wiki are disabled' do + it 'shows the project activity when issues and wiki are disabled' do visit project_path(project) - expect(page).to have_selector '.project-show-customize_workflow' + expect(page).to have_selector '.project-show-activity' end it 'shows the wiki when enabled' do diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb deleted file mode 100644 index 8f2fb9e827c..00000000000 --- a/spec/features/projects/wiki/markdown_preview_spec.rb +++ /dev/null @@ -1,168 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Projects > Wiki > User previews markdown changes', :js do - let_it_be(:user) { create(:user) } - let(:project) { create(:project, :wiki_repo, namespace: user.namespace) } - let(:wiki_page) { create(:wiki_page, wiki: project.wiki, title: 'home', content: '[some link](other-page)') } - let(:wiki_content) do - <<-HEREDOC -Some text so key event for [ does not trigger an incorrect replacement. -[regular link](regular) -[relative link 1](../relative) -[relative link 2](./relative) -[relative link 3](./e/f/relative) -[spaced link](title with spaces) - HEREDOC - end - - before do - project.add_maintainer(user) - - sign_in(user) - end - - context "while creating a new wiki page" do - context "when there are no spaces or hyphens in the page name" do - it "rewrites relative links as expected" do - create_wiki_page('a/b/c/d', content: wiki_content) - - expect(page).to have_content("regular link") - - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/regular\">regular link</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a/b/relative\">relative link 1</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a/b/c/relative\">relative link 2</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a/b/c/e/f/relative\">relative link 3</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/title%20with%20spaces\">spaced link</a>") - end - end - - context "when there are spaces in the page name" do - it "rewrites relative links as expected" do - create_wiki_page('a page/b page/c page/d page', content: wiki_content) - - expect(page).to have_content("regular link") - - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/regular\">regular link</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/relative\">relative link 1</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/c-page/relative\">relative link 2</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/title%20with%20spaces\">spaced link</a>") - end - end - - context "when there are hyphens in the page name" do - it "rewrites relative links as expected" do - create_wiki_page('a-page/b-page/c-page/d-page', content: wiki_content) - - expect(page).to have_content("regular link") - - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/regular\">regular link</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/relative\">relative link 1</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/c-page/relative\">relative link 2</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/title%20with%20spaces\">spaced link</a>") - end - end - end - - context "while editing a wiki page" do - context "when there are no spaces or hyphens in the page name" do - it "rewrites relative links as expected" do - create_wiki_page('a/b/c/d') - click_link 'Edit' - - fill_in :wiki_content, with: wiki_content - click_on "Preview" - - expect(page).to have_content("regular link") - - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/regular\">regular link</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a/b/relative\">relative link 1</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a/b/c/relative\">relative link 2</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a/b/c/e/f/relative\">relative link 3</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/title%20with%20spaces\">spaced link</a>") - end - end - - context "when there are spaces in the page name" do - it "rewrites relative links as expected" do - create_wiki_page('a page/b page/c page/d page') - click_link 'Edit' - - fill_in :wiki_content, with: wiki_content - click_on "Preview" - - expect(page).to have_content("regular link") - - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/regular\">regular link</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/relative\">relative link 1</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/c-page/relative\">relative link 2</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/title%20with%20spaces\">spaced link</a>") - end - end - - context "when there are hyphens in the page name" do - it "rewrites relative links as expected" do - create_wiki_page('a-page/b-page/c-page/d-page') - click_link 'Edit' - - fill_in :wiki_content, with: wiki_content - click_on "Preview" - - expect(page).to have_content("regular link") - - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/regular\">regular link</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/relative\">relative link 1</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/c-page/relative\">relative link 2</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>") - expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/title%20with%20spaces\">spaced link</a>") - end - end - - context 'when rendering the preview' do - it 'renders content with CommonMark' do - create_wiki_page('a-page/b-page/c-page/common-mark') - click_link 'Edit' - - fill_in :wiki_content, with: "1. one\n - sublist\n" - click_on "Preview" - - # the above generates two separate lists (not embedded) in CommonMark - expect(page).to have_content("sublist") - expect(page).not_to have_xpath("//ol//li//ul") - end - end - end - - it "does not linkify double brackets inside code blocks as expected" do - wiki_content = <<-HEREDOC - `[[do_not_linkify]]` - ``` - [[also_do_not_linkify]] - ``` - HEREDOC - - create_wiki_page('linkify_test', wiki_content) - - expect(page).to have_content("do_not_linkify") - - expect(page.html).to include('[[do_not_linkify]]') - expect(page.html).to include('[[also_do_not_linkify]]') - end - - private - - def create_wiki_page(path, content = 'content') - visit project_wiki_path(project, wiki_page) - - click_link 'New page' - - fill_in :wiki_title, with: path - fill_in :wiki_content, with: content - - click_button 'Create page' - end -end diff --git a/spec/features/projects/wiki/shortcuts_spec.rb b/spec/features/projects/wiki/shortcuts_spec.rb deleted file mode 100644 index 170e7afb51f..00000000000 --- a/spec/features/projects/wiki/shortcuts_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Wiki shortcuts', :js do - let(:user) { create(:user) } - let(:project) { create(:project, :wiki_repo, namespace: user.namespace) } - let(:wiki_page) { create(:wiki_page, wiki: project.wiki, title: 'home', content: 'Home page') } - - before do - sign_in(user) - visit project_wiki_path(project, wiki_page) - end - - it 'Visit edit wiki page using "e" keyboard shortcut' do - find('body').native.send_key('e') - - expect(find('.wiki-page-title')).to have_content('Edit Page') - end -end diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb deleted file mode 100644 index eba1b63765a..00000000000 --- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb +++ /dev/null @@ -1,360 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -RSpec.describe "User creates wiki page" do - include WikiHelpers - - let(:user) { create(:user) } - let(:wiki) { ProjectWiki.new(project, user) } - let(:project) { create(:project) } - - before do - project.add_maintainer(user) - - sign_in(user) - end - - context "when wiki is empty" do - before do |example| - visit(project_wikis_path(project)) - - wait_for_svg_to_be_loaded(example) - - click_link "Create your first page" - end - - context "in a user namespace" do - let(:project) { create(:project, :wiki_repo, namespace: user.namespace) } - - it "shows validation error message" do - page.within(".wiki-form") do - fill_in(:wiki_content, with: "") - - click_on("Create page") - end - - expect(page).to have_content("The form contains the following error:").and have_content("Content can't be blank") - - page.within(".wiki-form") do - fill_in(:wiki_content, with: "[link test](test)") - - click_on("Create page") - end - - expect(page).to have_content("Home").and have_content("link test") - - click_link("link test") - - expect(page).to have_content("Create New Page") - end - - it "shows non-escaped link in the pages list" do - fill_in(:wiki_title, with: "one/two/three-test") - - page.within(".wiki-form") do - fill_in(:wiki_content, with: "wiki content") - - click_on("Create page") - end - - expect(current_path).to include("one/two/three-test") - expect(page).to have_xpath("//a[@href='/#{project.full_path}/-/wikis/one/two/three-test']") - end - - it "has `Create home` as a commit message", :js do - wait_for_requests - - expect(page).to have_field("wiki[message]", with: "Create home") - end - - it "creates a page from the home page" do - fill_in(:wiki_content, with: "[test](test)\n[GitLab API doc](api)\n[Rake tasks](raketasks)\n# Wiki header\n") - fill_in(:wiki_message, with: "Adding links to wiki") - - page.within(".wiki-form") do - click_button("Create page") - end - - expect(current_path).to eq(project_wiki_path(project, "home")) - expect(page).to have_content("test GitLab API doc Rake tasks Wiki header") - .and have_content("Home") - .and have_content("Last edited by #{user.name}") - .and have_header_with_correct_id_and_link(1, "Wiki header", "wiki-header") - - click_link("test") - - expect(current_path).to eq(project_wiki_path(project, "test")) - - page.within(:css, ".nav-text") do - expect(page).to have_content("Create New Page") - end - - click_link("Home") - - expect(current_path).to eq(project_wiki_path(project, "home")) - - click_link("GitLab API") - - expect(current_path).to eq(project_wiki_path(project, "api")) - - page.within(:css, ".nav-text") do - expect(page).to have_content("Create") - end - - click_link("Home") - - expect(current_path).to eq(project_wiki_path(project, "home")) - - click_link("Rake tasks") - - expect(current_path).to eq(project_wiki_path(project, "raketasks")) - - page.within(:css, ".nav-text") do - expect(page).to have_content("Create") - end - end - - it "creates ASCII wiki with LaTeX blocks", :js do - stub_application_setting(plantuml_url: "http://localhost", plantuml_enabled: true) - - ascii_content = <<~MD - :stem: latexmath - - [stem] - ++++ - \\sqrt{4} = 2 - ++++ - - another part - - [latexmath] - ++++ - \\beta_x \\gamma - ++++ - - stem:[2+2] is 4 - MD - - find("#wiki_format option[value=asciidoc]").select_option - - fill_in(:wiki_content, with: ascii_content) - - page.within(".wiki-form") do - click_button("Create page") - end - - page.within ".md" do - expect(page).to have_selector(".katex", count: 3).and have_content("2+2 is 4") - end - end - - it 'creates a wiki page with Org markup', :aggregate_failures do - org_content = <<~ORG - * Heading - ** Subheading - [[home][Link to Home]] - ORG - - page.within('.wiki-form') do - find('#wiki_format option[value=org]').select_option - fill_in(:wiki_content, with: org_content) - click_button('Create page') - end - - expect(page).to have_selector('h1', text: 'Heading') - expect(page).to have_selector('h2', text: 'Subheading') - expect(page).to have_link('Link to Home', href: "/#{project.full_path}/-/wikis/home") - end - - it_behaves_like 'wiki file attachments' - end - - context "in a group namespace", :js do - let(:project) { create(:project, :wiki_repo, namespace: create(:group, :public)) } - - it "has `Create home` as a commit message" do - wait_for_requests - - expect(page).to have_field("wiki[message]", with: "Create home") - end - - it "creates a page from the home page" do - page.within(".wiki-form") do - fill_in(:wiki_content, with: "My awesome wiki!") - - click_button("Create page") - end - - expect(page).to have_content("Home") - .and have_content("Last edited by #{user.name}") - .and have_content("My awesome wiki!") - end - end - end - - context "when wiki is not empty", :js do - before do - create(:wiki_page, wiki: wiki, title: 'home', content: 'Home page') - - visit(project_wikis_path(project)) - end - - context "in a user namespace" do - let(:project) { create(:project, :wiki_repo, namespace: user.namespace) } - - context "via the `new wiki page` page" do - it "creates a page with a single word" do - click_link("New page") - - page.within(".wiki-form") do - fill_in(:wiki_title, with: "foo") - fill_in(:wiki_content, with: "My awesome wiki!") - end - - # Commit message field should have correct value. - expect(page).to have_field("wiki[message]", with: "Create foo") - - click_button("Create page") - - expect(page).to have_content("foo") - .and have_content("Last edited by #{user.name}") - .and have_content("My awesome wiki!") - end - - it "creates a page with spaces in the name" do - click_link("New page") - - page.within(".wiki-form") do - fill_in(:wiki_title, with: "Spaces in the name") - fill_in(:wiki_content, with: "My awesome wiki!") - end - - # Commit message field should have correct value. - expect(page).to have_field("wiki[message]", with: "Create Spaces in the name") - - click_button("Create page") - - expect(page).to have_content("Spaces in the name") - .and have_content("Last edited by #{user.name}") - .and have_content("My awesome wiki!") - end - - it "creates a page with hyphens in the name" do - click_link("New page") - - page.within(".wiki-form") do - fill_in(:wiki_title, with: "hyphens-in-the-name") - fill_in(:wiki_content, with: "My awesome wiki!") - end - - # Commit message field should have correct value. - expect(page).to have_field("wiki[message]", with: "Create hyphens in the name") - - page.within(".wiki-form") do - fill_in(:wiki_content, with: "My awesome wiki!") - - click_button("Create page") - end - - expect(page).to have_content("hyphens in the name") - .and have_content("Last edited by #{user.name}") - .and have_content("My awesome wiki!") - end - end - - it "shows the emoji autocompletion dropdown" do - click_link("New page") - - page.within(".wiki-form") do - find("#wiki_content").native.send_keys("") - - fill_in(:wiki_content, with: ":") - end - - expect(page).to have_selector(".atwho-view") - end - end - - context "in a group namespace" do - let(:project) { create(:project, :wiki_repo, namespace: create(:group, :public)) } - - context "via the `new wiki page` page" do - it "creates a page" do - click_link("New page") - - page.within(".wiki-form") do - fill_in(:wiki_title, with: "foo") - fill_in(:wiki_content, with: "My awesome wiki!") - end - - # Commit message field should have correct value. - expect(page).to have_field("wiki[message]", with: "Create foo") - - click_button("Create page") - - expect(page).to have_content("foo") - .and have_content("Last edited by #{user.name}") - .and have_content("My awesome wiki!") - end - end - end - end - - describe 'sidebar feature' do - context 'when there are some existing pages' do - before do - create(:wiki_page, wiki: wiki, title: 'home', content: 'home') - create(:wiki_page, wiki: wiki, title: 'another', content: 'another') - end - - it 'renders a default sidebar when there is no customized sidebar' do - visit(project_wikis_path(project)) - - expect(page).to have_content('another') - expect(page).not_to have_link('View All Pages') - end - - context 'when there is a customized sidebar' do - before do - create(:wiki_page, wiki: wiki, title: '_sidebar', content: 'My customized sidebar') - end - - it 'renders my customized sidebar instead of the default one' do - visit(project_wikis_path(project)) - - expect(page).to have_content('My customized sidebar') - expect(page).not_to have_content('Another') - end - end - end - - context 'when there are 15 existing pages' do - before do - (1..5).each { |i| create(:wiki_page, wiki: wiki, title: "my page #{i}") } - (6..10).each { |i| create(:wiki_page, wiki: wiki, title: "parent/my page #{i}") } - (11..15).each { |i| create(:wiki_page, wiki: wiki, title: "grandparent/parent/my page #{i}") } - end - - it 'shows all pages in the sidebar' do - visit(project_wikis_path(project)) - - (1..15).each { |i| expect(page).to have_content("my page #{i}") } - expect(page).not_to have_link('View All Pages') - end - - context 'when there are more than 15 existing pages' do - before do - create(:wiki_page, wiki: wiki, title: 'my page 16') - end - - it 'shows the first 15 pages in the sidebar' do - visit(project_wikis_path(project)) - - expect(page).to have_text('my page', count: 15) - expect(page).to have_link('View All Pages') - end - end - end - end -end diff --git a/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb b/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb deleted file mode 100644 index a5d865d581b..00000000000 --- a/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'User deletes wiki page', :js do - let(:user) { create(:user) } - let(:project) { create(:project, :wiki_repo, namespace: user.namespace) } - let(:wiki_page) { create(:wiki_page, wiki: project.wiki) } - - before do - sign_in(user) - visit(project_wiki_path(project, wiki_page)) - end - - it 'deletes a page' do - click_on('Edit') - click_on('Delete') - find('.modal-footer .btn-danger').click - - expect(page).to have_content('Page was successfully deleted') - end -end diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb deleted file mode 100644 index fdab63a56b8..00000000000 --- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb +++ /dev/null @@ -1,263 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'User updates wiki page' do - include WikiHelpers - - let(:user) { create(:user) } - - before do - project.add_maintainer(user) - sign_in(user) - end - - context 'when wiki is empty' do - before do |example| - visit(project_wikis_path(project)) - - wait_for_svg_to_be_loaded(example) - - click_link "Create your first page" - end - - context 'in a user namespace' do - let(:project) { create(:project, :wiki_repo) } - - it 'redirects back to the home edit page' do - page.within(:css, '.wiki-form .form-actions') do - click_on('Cancel') - end - - expect(current_path).to eq wiki_path(project.wiki) - end - - it 'updates a page that has a path', :js do - fill_in(:wiki_title, with: 'one/two/three-test') - - page.within '.wiki-form' do - fill_in(:wiki_content, with: 'wiki content') - click_on('Create page') - end - - expect(current_path).to include('one/two/three-test') - expect(find('.wiki-pages')).to have_content('three') - - first(:link, text: 'three').click - - expect(find('.nav-text')).to have_content('three') - - click_on('Edit') - - expect(current_path).to include('one/two/three-test') - expect(page).to have_content('Edit Page') - - fill_in('Content', with: 'Updated Wiki Content') - click_on('Save changes') - - expect(page).to have_content('Updated Wiki Content') - end - - it_behaves_like 'wiki file attachments' - end - end - - context 'when wiki is not empty' do - let(:project_wiki) { create(:project_wiki, project: project, user: project.creator) } - let!(:wiki_page) { create(:wiki_page, wiki: project_wiki, title: 'home', content: 'Home page') } - - before do - visit(project_wikis_path(project)) - - click_link('Edit') - end - - context 'in a user namespace' do - let(:project) { create(:project, :wiki_repo) } - - it 'updates a page', :js do - # Commit message field should have correct value. - expect(page).to have_field('wiki[message]', with: 'Update home') - - fill_in(:wiki_content, with: 'My awesome wiki!') - click_button('Save changes') - - expect(page).to have_content('Home') - expect(page).to have_content("Last edited by #{user.name}") - expect(page).to have_content('My awesome wiki!') - end - - it 'updates the commit message as the title is changed', :js do - fill_in(:wiki_title, with: '& < > \ \ { } &') - - expect(page).to have_field('wiki[message]', with: 'Update & < > \ \ { } &') - end - - it 'correctly escapes the commit message entities', :js do - fill_in(:wiki_title, with: 'Wiki title') - - expect(page).to have_field('wiki[message]', with: 'Update Wiki title') - end - - it 'shows a validation error message' do - fill_in(:wiki_content, with: '') - click_button('Save changes') - - expect(page).to have_selector('.wiki-form') - expect(page).to have_content('Edit Page') - expect(page).to have_content('The form contains the following error:') - expect(page).to have_content("Content can't be blank") - expect(find('textarea#wiki_content').value).to eq('') - end - - it 'shows the emoji autocompletion dropdown', :js do - find('#wiki_content').native.send_keys('') - fill_in(:wiki_content, with: ':') - - expect(page).to have_selector('.atwho-view') - end - - it 'shows the error message' do - wiki_page.update(content: 'Update') - - click_button('Save changes') - - expect(page).to have_content('Someone edited the page the same time you did.') - end - - it 'updates a page' do - fill_in('Content', with: 'Updated Wiki Content') - click_on('Save changes') - - expect(page).to have_content('Updated Wiki Content') - end - - it 'cancels editing of a page' do - page.within(:css, '.wiki-form .form-actions') do - click_on('Cancel') - end - - expect(current_path).to eq(project_wiki_path(project, wiki_page)) - end - - it_behaves_like 'wiki file attachments' - end - - context 'in a group namespace' do - let(:project) { create(:project, :wiki_repo, namespace: create(:group, :public)) } - - it 'updates a page', :js do - # Commit message field should have correct value. - expect(page).to have_field('wiki[message]', with: 'Update home') - - fill_in(:wiki_content, with: 'My awesome wiki!') - - click_button('Save changes') - - expect(page).to have_content('Home') - expect(page).to have_content("Last edited by #{user.name}") - expect(page).to have_content('My awesome wiki!') - end - - it_behaves_like 'wiki file attachments' - end - end - - context 'when the page is in a subdir' do - let!(:project) { create(:project, :wiki_repo) } - let(:project_wiki) { create(:project_wiki, project: project, user: project.creator) } - let(:page_name) { 'page_name' } - let(:page_dir) { "foo/bar/#{page_name}" } - let!(:wiki_page) { create(:wiki_page, wiki: project_wiki, title: page_dir, content: 'Home page') } - - before do - visit(project_wiki_edit_path(project, wiki_page)) - end - - it 'moves the page to the root folder' do - fill_in(:wiki_title, with: "/#{page_name}") - - click_button('Save changes') - - expect(current_path).to eq(project_wiki_path(project, page_name)) - end - - it 'moves the page to other dir' do - new_page_dir = "foo1/bar1/#{page_name}" - - fill_in(:wiki_title, with: new_page_dir) - - click_button('Save changes') - - expect(current_path).to eq(project_wiki_path(project, new_page_dir)) - end - - it 'remains in the same place if title has not changed' do - original_path = project_wiki_path(project, wiki_page) - - fill_in(:wiki_title, with: page_name) - - click_button('Save changes') - - expect(current_path).to eq(original_path) - end - - it 'can be moved to a different dir with a different name' do - new_page_dir = "foo1/bar1/new_page_name" - - fill_in(:wiki_title, with: new_page_dir) - - click_button('Save changes') - - expect(current_path).to eq(project_wiki_path(project, new_page_dir)) - end - - it 'can be renamed and moved to the root folder' do - new_name = 'new_page_name' - - fill_in(:wiki_title, with: "/#{new_name}") - - click_button('Save changes') - - expect(current_path).to eq(project_wiki_path(project, new_name)) - end - - it 'squishes the title before creating the page' do - new_page_dir = " foo1 / bar1 / #{page_name} " - - fill_in(:wiki_title, with: new_page_dir) - - click_button('Save changes') - - expect(current_path).to eq(project_wiki_path(project, "foo1/bar1/#{page_name}")) - end - - it_behaves_like 'wiki file attachments' - end - - context 'when an existing page exceeds the content size limit' do - let_it_be(:project) { create(:project, :wiki_repo) } - let!(:wiki_page) { create(:wiki_page, wiki: project.wiki, content: "one\ntwo\nthree") } - - before do - stub_application_setting(wiki_page_max_content_bytes: 10) - - visit wiki_page_path(wiki_page.wiki, wiki_page, action: :edit) - end - - it 'allows changing the title if the content does not change' do - fill_in 'Title', with: 'new title' - click_on 'Save changes' - - expect(page).to have_content('Wiki was successfully updated.') - end - - it 'shows a validation error when trying to change the content' do - fill_in 'Content', with: 'new content' - click_on 'Save changes' - - expect(page).to have_content('The form contains the following error:') - expect(page).to have_content('Content is too long (11 Bytes). The maximum size is 10 Bytes.') - end - end -end diff --git a/spec/features/projects/wiki/user_views_wiki_empty_spec.rb b/spec/features/projects/wiki/user_views_wiki_empty_spec.rb index 0af40a2d760..1f460f39267 100644 --- a/spec/features/projects/wiki/user_views_wiki_empty_spec.rb +++ b/spec/features/projects/wiki/user_views_wiki_empty_spec.rb @@ -2,108 +2,86 @@ require 'spec_helper' -RSpec.describe 'User views empty wiki' do - let(:user) { create(:user) } - let(:confluence_link) { 'Enable the Confluence Wiki integration' } - let(:element) { page.find('.row.empty-state') } - - shared_examples 'empty wiki and accessible issues' do - it 'show "issue tracker" message' do - visit(project_wikis_path(project)) - - expect(element).to have_content('This project has no wiki pages') - expect(element).to have_content('You must be a project member') - expect(element).to have_content('improve the wiki for this project') - expect(element).to have_link("issue tracker", href: project_issues_path(project)) - expect(element).to have_link("Suggest wiki improvement", href: new_project_issue_path(project)) - expect(element).to have_no_link(confluence_link) - end - end - - shared_examples 'empty wiki and non-accessible issues' do - it 'does not show "issue tracker" message' do - visit(project_wikis_path(project)) +RSpec.describe 'Project > User views empty wiki' do + let_it_be(:user) { create(:user) } - expect(element).to have_content('This project has no wiki pages') - expect(element).to have_content('You must be a project member') - expect(element).to have_no_link('Suggest wiki improvement') - expect(element).to have_no_link(confluence_link) - end - end + let(:wiki) { create(:project_wiki, project: project) } - context 'when user is logged out and issue tracker is public' do - let(:project) { create(:project, :public, :wiki_repo) } + it_behaves_like 'User views empty wiki' do + context 'when project is public' do + let(:project) { create(:project, :public) } - it_behaves_like 'empty wiki and accessible issues' - end + it_behaves_like 'empty wiki message', issuable: true - context 'when user is logged in and not a member' do - let(:project) { create(:project, :public, :wiki_repo) } + context 'when issue tracker is private' do + let(:project) { create(:project, :public, :issues_private) } - before do - sign_in(user) - end + it_behaves_like 'empty wiki message', issuable: false + end - it_behaves_like 'empty wiki and accessible issues' - end + context 'when issue tracker is disabled' do + let(:project) { create(:project, :public, :issues_disabled) } - context 'when issue tracker is private' do - let(:project) { create(:project, :public, :wiki_repo, :issues_private) } + it_behaves_like 'empty wiki message', issuable: false + end - it_behaves_like 'empty wiki and non-accessible issues' - end + context 'and user is logged in' do + before do + sign_in(user) + end - context 'when issue tracker is disabled' do - let(:project) { create(:project, :public, :wiki_repo, :issues_disabled) } + context 'and user is not a member' do + it_behaves_like 'empty wiki message', issuable: true + end - it_behaves_like 'empty wiki and non-accessible issues' - end + context 'and user is a member' do + before do + project.add_developer(user) + end - context 'when user is logged in and a member' do - let(:project) { create(:project, :public) } - - before do - sign_in(user) - project.add_developer(user) + it_behaves_like 'empty wiki message', writable: true, issuable: true + end + end end - it 'shows "create first page" message' do - visit(project_wikis_path(project)) - - expect(element).to have_content('your project', count: 2) + context 'when project is private' do + let(:project) { create(:project, :private) } - element.click_link 'Create your first page' + it_behaves_like 'wiki is not found' - expect(page).to have_button('Create page') - end + context 'and user is logged in' do + before do + sign_in(user) + end - it 'does not show the "enable confluence" button' do - visit(project_wikis_path(project)) + context 'and user is not a member' do + it_behaves_like 'wiki is not found' + end - expect(element).to have_no_link(confluence_link) - end - end + context 'and user is a member' do + before do + project.add_developer(user) + end - context 'when user is logged in and an admin' do - let(:project) { create(:project, :public, :wiki_repo) } + it_behaves_like 'empty wiki message', writable: true, issuable: true + end - before do - sign_in(user) - project.add_maintainer(user) - end - - it 'shows the "enable confluence" button' do - visit(project_wikis_path(project)) - - expect(element).to have_link(confluence_link) - end + context 'and user is a maintainer' do + before do + project.add_maintainer(user) + end - it 'does not show "enable confluence" button if confluence is already enabled' do - create(:confluence_service, project: project) + it_behaves_like 'empty wiki message', writable: true, issuable: true, confluence: true - visit(project_wikis_path(project)) + context 'and Confluence is already enabled' do + before do + create(:confluence_service, project: project) + end - expect(element).to have_no_link(confluence_link) + it_behaves_like 'empty wiki message', writable: true, issuable: true, confluence: false + end + end + end end end end diff --git a/spec/features/projects/wiki/user_views_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_page_spec.rb deleted file mode 100644 index e93689af0aa..00000000000 --- a/spec/features/projects/wiki/user_views_wiki_page_spec.rb +++ /dev/null @@ -1,276 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'User views a wiki page' do - include WikiHelpers - - let(:user) { create(:user) } - let(:project) { create(:project, :wiki_repo, namespace: user.namespace) } - let(:path) { 'image.png' } - let(:wiki) { project.wiki } - let(:wiki_page) do - create(:wiki_page, - wiki: wiki, - title: 'home', content: "Look at this [image](#{path})\n\n ![alt text](#{path})") - end - - before do - project.add_maintainer(user) - sign_in(user) - end - - context 'when wiki is empty', :js do - before do - visit project_wikis_path(project) - - wait_for_svg_to_be_loaded - - click_link "Create your first page" - - fill_in(:wiki_title, with: 'one/two/three-test') - - page.within('.wiki-form') do - fill_in(:wiki_content, with: 'wiki content') - click_on('Create page') - end - - expect(page).to have_content('Wiki was successfully updated.') - end - - it 'shows the history of a page that has a path' do - expect(current_path).to include('one/two/three-test') - - first(:link, text: 'three').click - click_on('Page history') - - expect(current_path).to include('one/two/three-test') - - page.within(:css, '.nav-text') do - expect(page).to have_content('History') - end - end - - it 'shows an old version of a page' do - expect(current_path).to include('one/two/three-test') - expect(find('.wiki-pages')).to have_content('three') - - first(:link, text: 'three').click - - expect(find('.nav-text')).to have_content('three') - - click_on('Edit') - - expect(current_path).to include('one/two/three-test') - expect(page).to have_content('Edit Page') - - fill_in('Content', with: 'Updated Wiki Content') - click_on('Save changes') - - expect(page).to have_content('Wiki was successfully updated.') - - click_on('Page history') - - within('.nav-text') do - expect(page).to have_content('History') - end - - within('.wiki-history') do - expect(page).to have_css('a[href*="?version_id"]', count: 4) - end - end - end - - context 'when a page does not have history' do - before do - visit(project_wiki_path(project, wiki_page)) - end - - it 'shows all the pages' do - expect(page).to have_content(user.name) - expect(find('.wiki-pages')).to have_content(wiki_page.title.capitalize) - end - - context 'shows a file stored in a page' do - let(:path) { upload_file_to_wiki(project, user, 'dk.png') } - - it do - expect(page).to have_xpath("//img[@data-src='#{wiki.wiki_base_path}/#{path}']") - expect(page).to have_link('image', href: "#{wiki.wiki_base_path}/#{path}") - - click_on('image') - - expect(current_path).to match("wikis/#{path}") - expect(page).not_to have_xpath('/html') # Page should render the image which means there is no html involved - end - end - - it 'shows the creation page if file does not exist' do - expect(page).to have_link('image', href: "#{wiki.wiki_base_path}/#{path}") - - click_on('image') - - expect(current_path).to match("wikis/#{path}") - expect(page).to have_content('Create New Page') - end - end - - context 'when a page has history' do - before do - wiki_page.update(message: 'updated home', content: 'updated [some link](other-page)') # rubocop:disable Rails/SaveBang - end - - it 'shows the page history' do - visit(project_wiki_path(project, wiki_page)) - - expect(page).to have_selector('a.btn', text: 'Edit') - - click_on('Page history') - - expect(page).to have_content(user.name) - expect(page).to have_content("#{user.username} created page: home") - expect(page).to have_content('updated home') - end - - it 'does not show the "Edit" button' do - visit(project_wiki_path(project, wiki_page, version_id: wiki_page.versions.last.id)) - - expect(page).not_to have_selector('a.btn', text: 'Edit') - end - - context 'show the diff' do - def expect_diff_links(commit) - diff_path = wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff) - - expect(page).to have_link('Hide whitespace changes', href: "#{diff_path}&w=1") - expect(page).to have_link('Inline', href: "#{diff_path}&view=inline") - expect(page).to have_link('Side-by-side', href: "#{diff_path}&view=parallel") - expect(page).to have_link("View page @ #{commit.short_id}", href: wiki_page_path(wiki, wiki_page, version_id: commit)) - expect(page).to have_css('.diff-file[data-blob-diff-path="%s"]' % diff_path) - end - - it 'links to the correct diffs' do - visit project_wiki_history_path(project, wiki_page) - - commit1 = wiki.commit('HEAD^') - commit2 = wiki.commit - - expect(page).to have_link('created page: home', href: wiki_page_path(wiki, wiki_page, version_id: commit1, action: :diff)) - expect(page).to have_link('updated home', href: wiki_page_path(wiki, wiki_page, version_id: commit2, action: :diff)) - end - - it 'between the current and the previous version of a page' do - commit = wiki.commit - visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff) - - expect(page).to have_content('by John Doe') - expect(page).to have_content('updated home') - expect(page).to have_content('Showing 1 changed file with 1 addition and 3 deletions') - expect(page).to have_content('some link') - - expect_diff_links(commit) - end - - it 'between two old versions of a page' do - wiki_page.update(message: 'latest home change', content: 'updated [another link](other-page)') # rubocop:disable Rails/SaveBang: - commit = wiki.commit('HEAD^') - visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff) - - expect(page).to have_content('by John Doe') - expect(page).to have_content('updated home') - expect(page).to have_content('Showing 1 changed file with 1 addition and 3 deletions') - expect(page).to have_content('some link') - expect(page).not_to have_content('latest home change') - expect(page).not_to have_content('another link') - - expect_diff_links(commit) - end - - it 'for the oldest version of a page' do - commit = wiki.commit('HEAD^') - visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff) - - expect(page).to have_content('by John Doe') - expect(page).to have_content('created page: home') - expect(page).to have_content('Showing 1 changed file with 4 additions and 0 deletions') - expect(page).to have_content('Look at this') - - expect_diff_links(commit) - end - end - end - - context 'when a page has special characters in its title' do - let(:title) { '<foo> !@#$%^&*()[]{}=_+\'"\\|<>? <bar>' } - - before do - wiki_page.update(title: title ) # rubocop:disable Rails/SaveBang - end - - it 'preserves the special characters' do - visit(project_wiki_path(project, wiki_page)) - - expect(page).to have_css('.wiki-page-title', text: title) - expect(page).to have_css('.wiki-pages li', text: title) - end - end - - context 'when a page has XSS in its title or content' do - let(:title) { '<script>alert("title")<script>' } - - before do - wiki_page.update(title: title, content: 'foo <script>alert("content")</script> bar') # rubocop:disable Rails/SaveBang - end - - it 'safely displays the page' do - visit(project_wiki_path(project, wiki_page)) - - expect(page).to have_css('.wiki-page-title', text: title) - expect(page).to have_content('foo bar') - end - end - - context 'when a page has XSS in its message' do - before do - wiki_page.update(message: '<script>alert(true)<script>', content: 'XSS update') # rubocop:disable Rails/SaveBang - end - - it 'safely displays the message' do - visit(project_wiki_history_path(project, wiki_page)) - - expect(page).to have_content('<script>alert(true)<script>') - end - end - - context 'when page has invalid content encoding' do - let(:content) { (+'whatever').force_encoding('ISO-8859-1') } - - before do - allow(Gitlab::EncodingHelper).to receive(:encode!).and_return(content) - - visit(project_wiki_path(project, wiki_page)) - end - - it 'does not show "Edit" button' do - expect(page).not_to have_selector('a.btn', text: 'Edit') - end - - it 'shows error' do - page.within(:css, '.flash-notice') do - expect(page).to have_content('The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.') - end - end - end - - it 'opens a default wiki page', :js do - visit project_path(project) - - find('.shortcuts-wiki').click - - wait_for_svg_to_be_loaded - - click_link "Create your first page" - - expect(page).to have_content('Create New Page') - end -end diff --git a/spec/features/projects/wiki/user_views_wiki_pages_spec.rb b/spec/features/projects/wiki/user_views_wiki_pages_spec.rb deleted file mode 100644 index 4f29ae0cc8a..00000000000 --- a/spec/features/projects/wiki/user_views_wiki_pages_spec.rb +++ /dev/null @@ -1,91 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'User views wiki pages' do - include WikiHelpers - - let(:user) { create(:user) } - let(:project) { create(:project, :wiki_repo, namespace: user.namespace) } - - let!(:wiki_page1) do - create(:wiki_page, wiki: project.wiki, title: '3 home', content: '3') - end - - let!(:wiki_page2) do - create(:wiki_page, wiki: project.wiki, title: '1 home', content: '1') - end - - let!(:wiki_page3) do - create(:wiki_page, wiki: project.wiki, title: '2 home', content: '2') - end - - let(:pages) do - page.find('.wiki-pages-list').all('li').map { |li| li.find('a') } - end - - before do - project.add_maintainer(user) - sign_in(user) - visit(project_wikis_pages_path(project)) - end - - context 'ordered by title' do - let(:pages_ordered_by_title) { [wiki_page2, wiki_page3, wiki_page1] } - - context 'asc' do - it 'pages are displayed in direct order' do - pages.each.with_index do |page_title, index| - expect(page_title.text).to eq(pages_ordered_by_title[index].title) - end - end - end - - context 'desc' do - before do - page.within('.wiki-sort-dropdown') do - page.find('.rspec-reverse-sort').click - end - end - - it 'pages are displayed in reversed order' do - pages.reverse_each.with_index do |page_title, index| - expect(page_title.text).to eq(pages_ordered_by_title[index].title) - end - end - end - end - - context 'ordered by created_at' do - let(:pages_ordered_by_created_at) { [wiki_page1, wiki_page2, wiki_page3] } - - before do - page.within('.wiki-sort-dropdown') do - click_button('Title') - click_link('Created date') - end - end - - context 'asc' do - it 'pages are displayed in direct order' do - pages.each.with_index do |page_title, index| - expect(page_title.text).to eq(pages_ordered_by_created_at[index].title) - end - end - end - - context 'desc' do - before do - page.within('.wiki-sort-dropdown') do - page.find('.rspec-reverse-sort').click - end - end - - it 'pages are displayed in reversed order' do - pages.reverse_each.with_index do |page_title, index| - expect(page_title.text).to eq(pages_ordered_by_created_at[index].title) - end - end - end - end -end diff --git a/spec/features/projects/wiki/users_views_asciidoc_page_with_includes_spec.rb b/spec/features/projects/wiki/users_views_asciidoc_page_with_includes_spec.rb deleted file mode 100644 index 5c45e34595f..00000000000 --- a/spec/features/projects/wiki/users_views_asciidoc_page_with_includes_spec.rb +++ /dev/null @@ -1,79 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'User views AsciiDoc page with includes', :js do - let_it_be(:user) { create(:user) } - let_it_be(:wiki_content_selector) { '[data-qa-selector=wiki_page_content]' } - let(:project) { create(:project, :public, :wiki_repo) } - let!(:included_wiki_page) { create_wiki_page('included_page', content: 'Content from the included page')} - let!(:wiki_page) { create_wiki_page('home', content: "Content from the main page.\ninclude::included_page.asciidoc[]") } - - def create_wiki_page(title, content:) - attrs = { - title: title, - content: content, - format: :asciidoc - } - - create(:wiki_page, wiki: project.wiki, **attrs) - end - - before do - sign_in(user) - end - - context 'when the file being included exists' do - it 'includes the file contents' do - visit(project_wiki_path(project, wiki_page)) - - page.within(:css, wiki_content_selector) do - expect(page).to have_content('Content from the main page. Content from the included page') - end - end - - context 'when there are multiple versions of the wiki pages' do - before do - included_wiki_page.update(message: 'updated included file', content: 'Updated content from the included page') - wiki_page.update(message: 'updated wiki page', content: "Updated content from the main page.\ninclude::included_page.asciidoc[]") - end - - let(:latest_version_id) { wiki_page.versions.first.id } - let(:oldest_version_id) { wiki_page.versions.last.id } - - context 'viewing the latest version' do - it 'includes the latest content' do - visit(project_wiki_path(project, wiki_page, version_id: latest_version_id)) - - page.within(:css, wiki_content_selector) do - expect(page).to have_content('Updated content from the main page. Updated content from the included page') - end - end - end - - context 'viewing the original version' do - it 'includes the content from the original version' do - visit(project_wiki_path(project, wiki_page, version_id: oldest_version_id)) - - page.within(:css, wiki_content_selector) do - expect(page).to have_content('Content from the main page. Content from the included page') - end - end - end - end - end - - context 'when the file being included does not exist' do - before do - included_wiki_page.delete - end - - it 'outputs an error' do - visit(project_wiki_path(project, wiki_page)) - - page.within(:css, wiki_content_selector) do - expect(page).to have_content('Content from the main page. [ERROR: include::included_page.asciidoc[] - unresolved directive]') - end - end - end -end diff --git a/spec/features/projects/wikis_spec.rb b/spec/features/projects/wikis_spec.rb new file mode 100644 index 00000000000..1c66ad81145 --- /dev/null +++ b/spec/features/projects/wikis_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe 'Project wikis' do + let_it_be(:user) { create(:user) } + + let(:wiki) { create(:project_wiki, user: user, project: project) } + let(:project) { create(:project, namespace: user.namespace, creator: user) } + + it_behaves_like 'User creates wiki page' + it_behaves_like 'User deletes wiki page' + it_behaves_like 'User previews wiki changes' + it_behaves_like 'User updates wiki page' + it_behaves_like 'User uses wiki shortcuts' + it_behaves_like 'User views AsciiDoc page with includes' + it_behaves_like 'User views a wiki page' + it_behaves_like 'User views wiki pages' + it_behaves_like 'User views wiki sidebar' +end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 970500985ae..6baeb4ce368 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -6,25 +6,35 @@ RSpec.describe 'Project' do include ProjectForksHelper include MobileHelpers - describe 'creating from template' do + describe 'template' do let(:user) { create(:user) } - let(:template) { Gitlab::ProjectTemplate.find(:rails) } before do sign_in user visit new_project_path end - it "allows creation from templates", :js do - find('#create-from-template-tab').click - find("label[for=#{template.name}]").click - fill_in("project_name", with: template.name) + shared_examples 'creates from template' do |template, sub_template_tab = nil| + it "is created from template", :js do + find('#create-from-template-tab').click + find(".project-template #{sub_template_tab}").click if sub_template_tab + find("label[for=#{template.name}]").click + fill_in("project_name", with: template.name) - page.within '#content-body' do - click_button "Create project" + page.within '#content-body' do + click_button "Create project" + end + + expect(page).to have_content template.name end + end - expect(page).to have_content template.name + context 'create with project template' do + it_behaves_like 'creates from template', Gitlab::ProjectTemplate.find(:rails) + end + + context 'create with sample data template' do + it_behaves_like 'creates from template', Gitlab::SampleDataTemplate.find(:basic), '.sample-data-templates-tab' end end @@ -99,6 +109,15 @@ RSpec.describe 'Project' do expect(page).to have_css('.home-panel-description .is-expanded') end end + + context 'page description' do + before do + project.update_attribute(:description, '**Lorem** _ipsum_ dolor sit [amet](https://example.com)') + visit path + end + + it_behaves_like 'page meta description', 'Lorem ipsum dolor sit amet' + end end describe 'project topics' do diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb index f0707610c3f..3be01595502 100644 --- a/spec/features/protected_branches_spec.rb +++ b/spec/features/protected_branches_spec.rb @@ -9,6 +9,10 @@ RSpec.describe 'Protected Branches', :js do let(:admin) { create(:admin) } let(:project) { create(:project, :repository) } + before do + stub_feature_flags(deploy_keys_on_protected_branches: false) + end + context 'logged in as developer' do before do project.add_developer(user) @@ -27,7 +31,7 @@ RSpec.describe 'Protected Branches', :js do fill_in 'branch-search', with: 'fix' find('#branch-search').native.send_keys(:enter) - expect(page).to have_css('.btn-remove.disabled') + expect(page).to have_css('.btn-danger.disabled') end end end @@ -163,4 +167,14 @@ RSpec.describe 'Protected Branches', :js do include_examples "protected branches > access control > CE" end end + + context 'when the users for protected branches feature is off' do + before do + stub_licensed_features(protected_refs_for_users: false) + end + + include_examples 'when the deploy_keys_on_protected_branches FF is turned on' do + let(:all_dropdown_sections) { %w(Roles Deploy\ Keys) } + end + end end diff --git a/spec/features/reportable_note/snippets_spec.rb b/spec/features/reportable_note/snippets_spec.rb index 4d61e5d8285..92bf304ac86 100644 --- a/spec/features/reportable_note/snippets_spec.rb +++ b/spec/features/reportable_note/snippets_spec.rb @@ -7,7 +7,6 @@ RSpec.describe 'Reportable note on snippets', :js do let_it_be(:project) { create(:project) } before do - stub_feature_flags(snippets_vue: false) project.add_maintainer(user) sign_in(user) end diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index 0dff4c28270..6e18de3be7b 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -173,9 +173,9 @@ RSpec.describe 'Runners' do it 'user enables shared runners' do visit project_runners_path(project) - click_on 'Enable shared Runners' + click_on 'Enable shared runners' - expect(page.find('.shared-runners-description')).to have_content('Disable shared Runners') + expect(page.find('.shared-runners-description')).to have_content('Disable shared runners') end end diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb index 227e75088d2..a88043c98ac 100644 --- a/spec/features/search/user_searches_for_code_spec.rb +++ b/spec/features/search/user_searches_for_code_spec.rb @@ -21,6 +21,7 @@ RSpec.describe 'User searches for code' do expect(page).to have_selector('.results', text: 'application.js') expect(page).to have_selector('.file-content .code') expect(page).to have_selector("span.line[lang='javascript']") + expect(page).to have_link('application.js', href: /master\/files\/js\/application.js/) end context 'when on a project page', :js do diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb index cfda25b9ab4..5cbfacf4e48 100644 --- a/spec/features/search/user_uses_header_search_field_spec.rb +++ b/spec/features/search/user_uses_header_search_field_spec.rb @@ -30,6 +30,8 @@ RSpec.describe 'User uses header search field', :js do before do find('#search') find('body').native.send_keys('s') + + wait_for_all_requests end it 'shows the category search dropdown' do @@ -89,9 +91,7 @@ RSpec.describe 'User uses header search field', :js do context 'when entering text into the search field' do it 'does not display the category search dropdown' do - page.within('.search-input-wrap') do - fill_in('search', with: scope_name.first(4)) - end + fill_in_search(scope_name.first(4)) expect(page).not_to have_selector('.dropdown-header', text: /#{scope_name}/i) end @@ -105,9 +105,7 @@ RSpec.describe 'User uses header search field', :js do end it 'displays search options' do - page.within('.search-input-wrap') do - fill_in('search', with: 'test') - end + fill_in_search('test') expect(page).to have_selector(scoped_search_link('test')) end @@ -140,9 +138,7 @@ RSpec.describe 'User uses header search field', :js do end it 'displays search options' do - page.within('.search-input-wrap') do - fill_in('search', with: 'test') - end + fill_in_search('test') expect(page).to have_selector(scoped_search_link('test')) expect(page).to have_selector(scoped_search_link('test', group_id: group.id)) @@ -157,9 +153,7 @@ RSpec.describe 'User uses header search field', :js do end it 'displays search options' do - page.within('.search-input-wrap') do - fill_in('search', with: 'test') - end + fill_in_search('test') expect(page).to have_selector(scoped_search_link('test')) expect(page).not_to have_selector(scoped_search_link('test', group_id: project.namespace_id)) @@ -182,9 +176,7 @@ RSpec.describe 'User uses header search field', :js do end it 'displays search options' do - page.within('.search-input-wrap') do - fill_in('search', with: 'test') - end + fill_in_search('test') expect(page).to have_selector(scoped_search_link('test')) expect(page).to have_selector(scoped_search_link('test', group_id: group.id)) @@ -208,9 +200,7 @@ RSpec.describe 'User uses header search field', :js do end it 'displays search options' do - page.within('.search-input-wrap') do - fill_in('search', with: 'test') - end + fill_in_search('test') expect(page).to have_selector(scoped_search_link('test')) expect(page).to have_selector(scoped_search_link('test', group_id: subgroup.id)) diff --git a/spec/features/search/user_uses_search_filters_spec.rb b/spec/features/search/user_uses_search_filters_spec.rb index f39a1f8fe37..080cced21c3 100644 --- a/spec/features/search/user_uses_search_filters_spec.rb +++ b/spec/features/search/user_uses_search_filters_spec.rb @@ -12,12 +12,12 @@ RSpec.describe 'User uses search filters', :js do project.add_reporter(user) group.add_owner(user) sign_in(user) - - visit(search_path) end context 'when filtering by group' do it 'shows group projects' do + visit search_path + find('.js-search-group-dropdown').click wait_for_requests @@ -36,10 +36,27 @@ RSpec.describe 'User uses search filters', :js do expect(page).to have_link(group_project.full_name) end end + + context 'when the group filter is set' do + before do + visit search_path(search: "test", group_id: group.id, project_id: project.id) + end + + describe 'clear filter button' do + it 'removes Group and Project filters' do + link = find('[data-testid="group-filter"] .js-search-clear') + params = CGI.parse(URI.parse(link[:href]).query) + + expect(params).not_to include(:group_id, :project_id) + end + end + end end context 'when filtering by project' do it 'shows a project' do + visit search_path + page.within('.project-filter') do find('.js-search-project-dropdown').click @@ -50,5 +67,22 @@ RSpec.describe 'User uses search filters', :js do expect(find('.js-search-project-dropdown')).to have_content(project.full_name) end + + context 'when the project filter is set' do + before do + visit search_path(search: "test", project_id: project.id) + end + + let(:query) { { project_id: project.id } } + + describe 'clear filter button' do + it 'removes Project filters' do + link = find('.project-filter .js-search-clear') + params = CGI.parse(URI.parse(link[:href]).query) + + expect(params).not_to include(:project_id) + end + end + end end end diff --git a/spec/features/sentry_js_spec.rb b/spec/features/sentry_js_spec.rb index 1d277ba7b3c..aa0ad17340a 100644 --- a/spec/features/sentry_js_spec.rb +++ b/spec/features/sentry_js_spec.rb @@ -12,7 +12,7 @@ RSpec.describe 'Sentry' do expect(has_requested_sentry).to eq(false) end - it 'loads sentry if sentry is enabled' do + xit 'loads sentry if sentry is enabled' do stub_sentry_settings visit new_user_session_path diff --git a/spec/features/snippets/internal_snippet_spec.rb b/spec/features/snippets/internal_snippet_spec.rb index 3ce297ab22d..2fcd11c2a47 100644 --- a/spec/features/snippets/internal_snippet_spec.rb +++ b/spec/features/snippets/internal_snippet_spec.rb @@ -3,11 +3,8 @@ require 'spec_helper' RSpec.describe 'Internal Snippets', :js do - let(:internal_snippet) { create(:personal_snippet, :internal) } - - before do - stub_feature_flags(snippets_vue: false) - end + let(:internal_snippet) { create(:personal_snippet, :internal, :repository) } + let(:content) { internal_snippet.blobs.first.data.strip! } describe 'normal user' do before do @@ -17,13 +14,13 @@ RSpec.describe 'Internal Snippets', :js do it 'sees internal snippets' do visit snippet_path(internal_snippet) - expect(page).to have_content(internal_snippet.content) + expect(page).to have_content(content) end it 'sees raw internal snippets' do visit raw_snippet_path(internal_snippet) - expect(page).to have_content(internal_snippet.content) + expect(page).to have_content(content) end end end diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb index e98bb22d3ea..ce9a2d1461e 100644 --- a/spec/features/snippets/notes_on_personal_snippets_spec.rb +++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb @@ -18,7 +18,6 @@ RSpec.describe 'Comments on personal snippets', :js do end before do - stub_feature_flags(snippets_vue: false) sign_in user visit snippet_path(snippet) diff --git a/spec/features/snippets/private_snippets_spec.rb b/spec/features/snippets/private_snippets_spec.rb index 6b45f3485e7..03745c1025a 100644 --- a/spec/features/snippets/private_snippets_spec.rb +++ b/spec/features/snippets/private_snippets_spec.rb @@ -4,19 +4,18 @@ require 'spec_helper' RSpec.describe 'Private Snippets', :js do let(:user) { create(:user) } + let(:private_snippet) { create(:personal_snippet, :repository, :private, author: user) } + let(:content) { private_snippet.blobs.first.data.strip! } before do - stub_feature_flags(snippets_vue: false) sign_in(user) end it 'Private Snippet renders for creator' do - private_snippet = create(:personal_snippet, :private, author: user) - visit snippet_path(private_snippet) wait_for_requests - expect(page).to have_content(private_snippet.content) + expect(page).to have_content(content) expect(page).not_to have_css('.js-embed-btn') expect(page).not_to have_css('.js-share-btn') end diff --git a/spec/features/snippets/public_snippets_spec.rb b/spec/features/snippets/public_snippets_spec.rb index 4b72b33245d..d2dc85a9614 100644 --- a/spec/features/snippets/public_snippets_spec.rb +++ b/spec/features/snippets/public_snippets_spec.rb @@ -3,27 +3,24 @@ require 'spec_helper' RSpec.describe 'Public Snippets', :js do - before do - stub_feature_flags(snippets_vue: false) - end + let(:public_snippet) { create(:personal_snippet, :public, :repository) } + let(:content) { public_snippet.blobs.first.data.strip! } it 'Unauthenticated user should see public snippets' do - public_snippet = create(:personal_snippet, :public) + url = Gitlab::UrlBuilder.build(public_snippet) visit snippet_path(public_snippet) wait_for_requests - expect(page).to have_content(public_snippet.content) - expect(page).to have_css('.js-embed-btn', visible: false) - expect(page).to have_css('.js-share-btn', visible: false) - expect(page.find('.js-snippet-url-area')).to be_readonly + expect(page).to have_content(content) + click_button('Embed') + expect(page).to have_field('Embed', readonly: true, with: "<script src=\"#{url}.js\"></script>") + expect(page).to have_field('Share', readonly: true, with: url) end it 'Unauthenticated user should see raw public snippets' do - public_snippet = create(:personal_snippet, :public) - visit raw_snippet_path(public_snippet) - expect(page).to have_content(public_snippet.content) + expect(page).to have_content(content) end end diff --git a/spec/features/snippets/show_spec.rb b/spec/features/snippets/show_spec.rb index 981ed12d540..2103d362f94 100644 --- a/spec/features/snippets/show_spec.rb +++ b/spec/features/snippets/show_spec.rb @@ -6,10 +6,6 @@ RSpec.describe 'Snippet', :js do let_it_be(:user) { create(:user) } let_it_be(:snippet) { create(:personal_snippet, :public, :repository, author: user) } - before do - stub_feature_flags(snippets_vue: false) - end - it_behaves_like 'show and render proper snippet blob' do let(:anchor) { nil } @@ -20,12 +16,8 @@ RSpec.describe 'Snippet', :js do end end - it_behaves_like 'showing user status' do - let(:file_path) { 'files/ruby/popen.rb' } - let(:user_with_status) { snippet.author } - - subject { visit snippet_path(snippet) } - end + # it_behaves_like 'showing user status' do + # This will be handled in https://gitlab.com/gitlab-org/gitlab/-/issues/262394 it_behaves_like 'does not show New Snippet button' do let(:file_path) { 'files/ruby/popen.rb' } diff --git a/spec/features/snippets/spam_snippets_spec.rb b/spec/features/snippets/spam_snippets_spec.rb index 1483ba4bf8f..54a56ac962c 100644 --- a/spec/features/snippets/spam_snippets_spec.rb +++ b/spec/features/snippets/spam_snippets_spec.rb @@ -2,9 +2,11 @@ require 'spec_helper' -RSpec.shared_examples_for 'snippet editor' do +RSpec.describe 'snippet editor with spam', skip: "Will be handled in https://gitlab.com/gitlab-org/gitlab/-/issues/217722" do include_context 'includes Spam constants' + let_it_be(:user) { create(:user) } + def description_field find('.js-description-input').find('input,textarea') end @@ -119,24 +121,3 @@ RSpec.shared_examples_for 'snippet editor' do end end end - -RSpec.describe 'User creates snippet', :js do - let_it_be(:user) { create(:user) } - - context 'Vue application' do - before do - stub_feature_flags(snippets_edit_vue: false) - end - - it_behaves_like "snippet editor" - end - - context 'non-Vue application' do - before do - stub_feature_flags(snippets_vue: false) - stub_feature_flags(snippets_edit_vue: false) - end - - it_behaves_like "snippet editor" - end -end diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb index eabca028b8c..1e51210c2b8 100644 --- a/spec/features/snippets/user_creates_snippet_spec.rb +++ b/spec/features/snippets/user_creates_snippet_spec.rb @@ -13,163 +13,127 @@ RSpec.describe 'User creates snippet', :js do let(:md_description) { 'My Snippet **Description**' } let(:description) { 'My Snippet Description' } let(:created_snippet) { Snippet.last } - let(:snippet_title_field) { 'personal_snippet_title' } + let(:snippet_title_field) { 'snippet-title' } - def description_field - find('.js-description-input').find('input,textarea') + before do + sign_in(user) + + visit new_snippet_path end - shared_examples 'snippet creation' do - def fill_form - snippet_fill_in_form(title: title, content: file_content, description: md_description) - end + def fill_form + snippet_fill_in_form(title: title, content: file_content, description: md_description) + end - it 'Authenticated user creates a snippet' do - fill_form + it 'Authenticated user creates a snippet' do + fill_form - click_button('Create snippet') - wait_for_requests + click_button('Create snippet') + wait_for_requests - expect(page).to have_content(title) - page.within(snippet_description_view_selector) do - expect(page).to have_content(description) - expect(page).to have_selector('strong') - end - expect(page).to have_content(file_content) + expect(page).to have_content(title) + page.within(snippet_description_view_selector) do + expect(page).to have_content(description) + expect(page).to have_selector('strong') end + expect(page).to have_content(file_content) + end - it 'uploads a file when dragging into textarea' do - fill_form - dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') - - expect(snippet_description_value).to have_content('banana_sample') - - click_button('Create snippet') - wait_for_requests - - link = find('a.no-attachment-icon img.js-lazy-loaded[alt="banana_sample"]')['src'] - expect(link).to match(%r{/uploads/-/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z}) + it 'uploads a file when dragging into textarea' do + fill_form + dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') - reqs = inspect_requests { visit("#{link}?ran=#{SecureRandom.base64(20)}") } - expect(reqs.first.status_code).to eq(200) - end + expect(snippet_description_value).to have_content('banana_sample') - context 'when the git operation fails' do - let(:error) { 'Error creating the snippet' } + click_button('Create snippet') + wait_for_requests - before do - allow_next_instance_of(Snippets::CreateService) do |instance| - allow(instance).to receive(:create_commit).and_raise(StandardError, error) - end + link = find('a.no-attachment-icon img.js-lazy-loaded[alt="banana_sample"]')['src'] + expect(link).to match(%r{/uploads/-/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z}) - fill_form - click_button('Create snippet') - wait_for_requests - end + reqs = inspect_requests { visit("#{link}?ran=#{SecureRandom.base64(20)}") } + expect(reqs.first.status_code).to eq(200) + end - it 'renders the new page and displays the error' do - expect(page).to have_content(error) - expect(page).to have_content('New Snippet') + context 'when the git operation fails' do + let(:error) { 'Error creating the snippet' } - action = find('form.snippet-form')['action'] - expect(action).to include("/snippets") - end - end - - context 'when snippets default visibility level is restricted' do - before do - stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PRIVATE], - default_snippet_visibility: Gitlab::VisibilityLevel::PRIVATE) + before do + allow_next_instance_of(Snippets::CreateService) do |instance| + allow(instance).to receive(:create_commit).and_raise(StandardError, error) end - it 'creates a snippet using the lowest available visibility level as default' do - visit new_snippet_path - - fill_form - - click_button('Create snippet') - wait_for_requests - - expect(find('.blob-content')).to have_content(file_content) - expect(Snippet.last.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) - end + fill_form + click_button('Create snippet') + wait_for_requests end - it_behaves_like 'personal snippet with references' do - let(:container) { snippet_description_view_selector } - let(:md_description) { references } + it 'renders the new page and displays the error' do + expect(page).to have_content(error) + expect(page).to have_content('New Snippet') - subject do - fill_form - click_button('Create snippet') - - wait_for_requests - end + action = find('form.snippet-form')['action'] + expect(action).to include("/snippets") end end - context 'Vue application' do - let(:snippet_description_field) { 'snippet-description' } - let(:snippet_description_view_selector) { '.snippet-header .snippet-description' } - + context 'when snippets default visibility level is restricted' do before do - sign_in(user) - - visit new_snippet_path + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PRIVATE], + default_snippet_visibility: Gitlab::VisibilityLevel::PRIVATE) end - it_behaves_like 'snippet creation' + it 'creates a snippet using the lowest available visibility level as default' do + visit new_snippet_path - it 'validation fails for the first time' do - fill_in snippet_title_field, with: title + fill_form - expect(page).not_to have_button('Create snippet') + click_button('Create snippet') + wait_for_requests - snippet_fill_in_form(title: title, content: file_content) - expect(page).to have_button('Create snippet') + expect(find('.blob-content')).to have_content(file_content) + expect(Snippet.last.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) end end - context 'non-Vue application' do - let(:snippet_description_field) { 'personal_snippet_description' } - let(:snippet_description_view_selector) { '.snippet-header .description' } + it_behaves_like 'personal snippet with references' do + let(:container) { snippet_description_view_selector } + let(:md_description) { references } - before do - stub_feature_flags(snippets_vue: false) - stub_feature_flags(snippets_edit_vue: false) - - sign_in(user) + subject do + fill_form + click_button('Create snippet') - visit new_snippet_path + wait_for_requests end + end - it_behaves_like 'snippet creation' + it 'validation fails for the first time' do + fill_in snippet_title_field, with: title - it 'validation fails for the first time' do - fill_in snippet_title_field, with: title - click_button('Create snippet') + expect(page).not_to have_button('Create snippet') - expect(page).to have_selector('#error_explanation') - end + snippet_fill_in_form(title: title, content: file_content) + expect(page).to have_button('Create snippet') + end - it 'previews a snippet with file' do - # Click placeholder first to expand full description field - description_field.click - fill_in snippet_description_field, with: 'My Snippet' - dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') - find('.js-md-preview-button').click + it 'previews a snippet with file' do + # Click placeholder first to expand full description field + snippet_fill_in_description('My Snippet') + dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') + find('.js-md-preview-button').click - page.within('.md-preview-holder') do - expect(page).to have_content('My Snippet') + page.within('.md-preview-holder') do + expect(page).to have_content('My Snippet') - link = find('a.no-attachment-icon img.js-lazy-loaded[alt="banana_sample"]')['src'] - expect(link).to match(%r{/uploads/-/system/user/#{user.id}/\h{32}/banana_sample\.gif\z}) + link = find('a.no-attachment-icon img.js-lazy-loaded[alt="banana_sample"]')['src'] + expect(link).to match(%r{/uploads/-/system/user/#{user.id}/\h{32}/banana_sample\.gif\z}) - # Adds a cache buster for checking if the image exists as Selenium is now handling the cached requests - # not anymore as requests when they come straight from memory cache. - reqs = inspect_requests { visit("#{link}?ran=#{SecureRandom.base64(20)}") } - expect(reqs.first.status_code).to eq(200) - end + # Adds a cache buster for checking if the image exists as Selenium is now handling the cached requests + # not anymore as requests when they come straight from memory cache. + # accept_confirm is needed because of https://gitlab.com/gitlab-org/gitlab/-/issues/262102 + reqs = inspect_requests { accept_confirm { visit("#{link}?ran=#{SecureRandom.base64(20)}") } } + expect(reqs.first.status_code).to eq(200) end end end diff --git a/spec/features/snippets/user_deletes_snippet_spec.rb b/spec/features/snippets/user_deletes_snippet_spec.rb index d7cfc67df13..e896f7eb25b 100644 --- a/spec/features/snippets/user_deletes_snippet_spec.rb +++ b/spec/features/snippets/user_deletes_snippet_spec.rb @@ -2,21 +2,23 @@ require 'spec_helper' -RSpec.describe 'User deletes snippet' do +RSpec.describe 'User deletes snippet', :js do let(:user) { create(:user) } let(:content) { 'puts "test"' } - let(:snippet) { create(:personal_snippet, :public, content: content, author: user) } + let(:snippet) { create(:personal_snippet, :repository, :public, content: content, author: user) } before do sign_in(user) - stub_feature_flags(snippets_vue: false) - visit snippet_path(snippet) end it 'deletes the snippet' do - first(:link, 'Delete').click + expect(page).to have_content(snippet.title) + + click_button('Delete') + click_button('Delete snippet') + wait_for_requests expect(page).not_to have_content(snippet.title) end diff --git a/spec/features/snippets/user_edits_snippet_spec.rb b/spec/features/snippets/user_edits_snippet_spec.rb index 9a83eb58b63..a04c59b53d2 100644 --- a/spec/features/snippets/user_edits_snippet_spec.rb +++ b/spec/features/snippets/user_edits_snippet_spec.rb @@ -11,107 +11,77 @@ RSpec.describe 'User edits snippet', :js do let_it_be(:user) { create(:user) } let_it_be(:snippet, reload: true) { create(:personal_snippet, :repository, :public, file_name: file_name, content: content, author: user) } - let(:snippet_title_field) { 'personal_snippet_title' } + before do + sign_in(user) - shared_examples 'snippet editing' do - it 'displays the snippet blob path and content' do - blob = snippet.blobs.first - - aggregate_failures do - expect(snippet_get_first_blob_path).to eq blob.path - expect(snippet_get_first_blob_value).to have_content(blob.data.strip) - end - end - - it 'updates the snippet' do - fill_in snippet_title_field, with: 'New Snippet Title' - - click_button('Save changes') - wait_for_requests - - expect(page).to have_content('New Snippet Title') - end - - it 'updates the snippet with files attached' do - dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') - expect(snippet_description_value).to have_content('banana_sample') + visit edit_snippet_path(snippet) + wait_for_all_requests + end - click_button('Save changes') - wait_for_requests + it 'displays the snippet blob path and content' do + blob = snippet.blobs.first - link = find('a.no-attachment-icon img:not(.lazy)[alt="banana_sample"]')['src'] - expect(link).to match(%r{/uploads/-/system/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z}) + aggregate_failures do + expect(snippet_get_first_blob_path).to eq blob.path + expect(snippet_get_first_blob_value).to have_content(blob.data.strip) end + end - it 'updates the snippet to make it internal' do - choose 'Internal' - - click_button 'Save changes' - wait_for_requests + it 'updates the snippet' do + snippet_fill_in_title('New Snippet Title') + expect(page).not_to have_selector('.gl-spinner') - expect(page).to have_no_selector('[data-testid="lock-icon"]') - expect(page).to have_selector('[data-testid="shield-icon"]') - end + click_button('Save changes') + wait_for_requests - it 'updates the snippet to make it public' do - choose 'Public' + expect(page).to have_content('New Snippet Title') + end - click_button 'Save changes' - wait_for_requests + it 'updates the snippet with files attached' do + dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') + expect(snippet_description_value).to have_content('banana_sample') - expect(page).to have_no_selector('[data-testid="lock-icon"]') - expect(page).to have_selector('[data-testid="earth-icon"]') - end + click_button('Save changes') + wait_for_requests - context 'when the git operation fails' do - before do - allow_next_instance_of(Snippets::UpdateService) do |instance| - allow(instance).to receive(:create_commit).and_raise(StandardError, 'Error Message') - end + link = find('a.no-attachment-icon img:not(.lazy)[alt="banana_sample"]')['src'] + expect(link).to match(%r{/uploads/-/system/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z}) + end - fill_in snippet_title_field, with: 'New Snippet Title' - fill_in snippet_blob_path_field, with: 'new_file_name', match: :first + it 'updates the snippet to make it internal' do + snippet_fill_in_visibility('Internal') - click_button('Save changes') - end + click_button 'Save changes' + wait_for_requests - it 'renders edit page and displays the error' do - expect(page.find('.flash-container')).to have_content('Error updating the snippet - Error Message') - expect(page).to have_content('Edit Snippet') - end - end + expect(page).to have_no_selector('[data-testid="lock-icon"]') + expect(page).to have_selector('[data-testid="shield-icon"]') end - context 'Vue application' do - it_behaves_like 'snippet editing' do - let(:snippet_blob_path_field) { 'snippet_file_name' } - let(:snippet_blob_content_selector) { '.file-content' } - let(:snippet_description_field) { 'snippet-description' } + it 'updates the snippet to make it public' do + snippet_fill_in_visibility('Public') - before do - sign_in(user) + click_button 'Save changes' + wait_for_requests - visit edit_snippet_path(snippet) - wait_for_all_requests - end - end + expect(page).to have_no_selector('[data-testid="lock-icon"]') + expect(page).to have_selector('[data-testid="earth-icon"]') end - context 'non-Vue application' do - it_behaves_like 'snippet editing' do - let(:snippet_blob_path_field) { 'personal_snippet_file_name' } - let(:snippet_blob_content_selector) { '.file-content' } - let(:snippet_description_field) { 'personal_snippet_description' } + context 'when the git operation fails' do + before do + allow_next_instance_of(Snippets::UpdateService) do |instance| + allow(instance).to receive(:create_commit).and_raise(StandardError, 'Error Message') + end - before do - stub_feature_flags(snippets_vue: false) - stub_feature_flags(snippets_edit_vue: false) + snippet_fill_in_form(title: 'New Snippet Title', file_name: 'new_file_name') - sign_in(user) + click_button('Save changes') + end - visit edit_snippet_path(snippet) - wait_for_all_requests - end + it 'renders edit page and displays the error' do + expect(page.find('.flash-container')).to have_content('Error updating the snippet - Error Message') + expect(page).to have_content('Edit Snippet') end end end diff --git a/spec/features/snippets_spec.rb b/spec/features/snippets_spec.rb index 75309ca3e7c..8cdb4bc3344 100644 --- a/spec/features/snippets_spec.rb +++ b/spec/features/snippets_spec.rb @@ -18,11 +18,8 @@ RSpec.describe 'Snippets' do describe 'rendering engine' do let_it_be(:snippet) { create(:personal_snippet, :public) } - let(:snippets_vue_feature_flag_enabled) { true } before do - stub_feature_flags(snippets_vue: snippets_vue_feature_flag_enabled) - visit snippet_path(snippet) end @@ -30,14 +27,5 @@ RSpec.describe 'Snippets' do expect(page).to have_selector('#js-snippet-view') expect(page).not_to have_selector('.personal-snippets') end - - context 'when feature flag is disabled' do - let(:snippets_vue_feature_flag_enabled) { false } - - it 'renders HAML application and not Vue' do - expect(page).not_to have_selector('#js-snippet-view') - expect(page).to have_selector('.personal-snippets') - end - end end end diff --git a/spec/features/static_site_editor_spec.rb b/spec/features/static_site_editor_spec.rb index b67e47b6ac4..03085917d67 100644 --- a/spec/features/static_site_editor_spec.rb +++ b/spec/features/static_site_editor_spec.rb @@ -6,18 +6,71 @@ RSpec.describe 'Static Site Editor' do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :public, :repository) } + let(:sse_path) { project_show_sse_path(project, 'master/README.md') } + + before_all do + project.add_developer(user) + end + before do - project.add_maintainer(user) sign_in(user) + end + + context "when no config file is present" do + before do + visit sse_path + end - visit project_show_sse_path(project, 'master/README.md') + it 'renders SSE page with all generated config values and default config file values' do + node = page.find('#static-site-editor') + + # assert generated config values are present + expect(node['data-base-url']).to eq("/#{project.full_path}/-/sse/master%2FREADME.md") + expect(node['data-branch']).to eq('master') + expect(node['data-commit-id']).to match(/\A[0-9a-f]{40}\z/) + expect(node['data-is-supported-content']).to eq('true') + expect(node['data-merge-requests-illustration-path']) + .to match(%r{/assets/illustrations/merge_requests-.*\.svg}) + expect(node['data-namespace']).to eq(project.namespace.full_path) + expect(node['data-project']).to eq(project.path) + expect(node['data-project-id']).to eq(project.id.to_s) + + # assert default config file values are present + expect(node['data-image-upload-path']).to eq('source/images') + expect(node['data-mounts']).to eq('[{"source":"source","target":""}]') + expect(node['data-static-site-generator']).to eq('middleman') + end end - it 'renders Static Site Editor page with generated and file attributes' do - # assert generated config value is present - expect(page).to have_css('#static-site-editor[data-branch="master"]') + context "when a config file is present" do + let(:config_file_yml) do + <<~YAML + image_upload_path: custom-image-upload-path + mounts: + - source: source1 + target: "" + - source: source2 + target: target2 + static_site_generator: middleman + YAML + end + + before do + allow_next_instance_of(Repository) do |repository| + allow(repository).to receive(:blob_data_at).and_return(config_file_yml) + end + + visit sse_path + end + + it 'renders Static Site Editor page values read from config file' do + node = page.find('#static-site-editor') - # assert file config value is present - expect(page).to have_css('#static-site-editor[data-static-site-generator="middleman"]') + # assert user-specified config file values are present + expected_mounts = '[{"source":"source1","target":""},{"source":"source2","target":"target2"}]' + expect(node['data-image-upload-path']).to eq('custom-image-upload-path') + expect(node['data-mounts']).to eq(expected_mounts) + expect(node['data-static-site-generator']).to eq('middleman') + end end end diff --git a/spec/features/tags/developer_deletes_tag_spec.rb b/spec/features/tags/developer_deletes_tag_spec.rb index de9296bc08e..7c4c6f54685 100644 --- a/spec/features/tags/developer_deletes_tag_spec.rb +++ b/spec/features/tags/developer_deletes_tag_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Developer deletes tag' do +RSpec.describe 'Developer deletes tag', :js do let(:user) { create(:user) } let(:group) { create(:group) } let(:project) { create(:project, :repository, namespace: group) } @@ -13,11 +13,12 @@ RSpec.describe 'Developer deletes tag' do visit project_tags_path(project) end - context 'from the tags list page', :js do + context 'from the tags list page' do it 'deletes the tag' do expect(page).to have_content 'v1.1.0' - delete_tag 'v1.1.0' + container = page.find('.content .flex-row', text: 'v1.1.0') + delete_tag container expect(page).not_to have_content 'v1.1.0' end @@ -29,15 +30,15 @@ RSpec.describe 'Developer deletes tag' do expect(current_path).to eq( project_tag_path(project, 'v1.0.0')) - click_on 'Delete tag' + container = page.find('.nav-controls') + delete_tag container - expect(current_path).to eq( - project_tags_path(project)) + expect(current_path).to eq("#{project_tags_path(project)}/") expect(page).not_to have_content 'v1.0.0' end end - context 'when pre-receive hook fails', :js do + context 'when pre-receive hook fails' do before do allow_next_instance_of(Gitlab::GitalyClient::OperationService) do |instance| allow(instance).to receive(:rm_tag) @@ -46,15 +47,17 @@ RSpec.describe 'Developer deletes tag' do end it 'shows the error message' do - delete_tag 'v1.1.0' + container = page.find('.content .flex-row', text: 'v1.1.0') + delete_tag container expect(page).to have_content('Do not delete tags') end end - def delete_tag(tag) - page.within('.content') do - accept_confirm { find("li > .row-fixed-content.controls a.btn-remove[href='/#{project.full_path}/-/tags/#{tag}']").click } - end + def delete_tag(container) + container.find('.js-remove-tag').click + + page.within('.modal') { click_button('Delete tag') } + wait_for_requests end end diff --git a/spec/features/tags/developer_views_tags_spec.rb b/spec/features/tags/developer_views_tags_spec.rb index 4888611472c..6bae53afe6f 100644 --- a/spec/features/tags/developer_views_tags_spec.rb +++ b/spec/features/tags/developer_views_tags_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Developer views tags' do + include RepoHelpers + let(:user) { create(:user) } let(:group) { create(:group) } @@ -15,10 +17,13 @@ RSpec.describe 'Developer views tags' do let(:project) { create(:project_empty_repo, namespace: group) } before do - visit project_path(project) - click_on 'Add README' - fill_in :commit_message, with: 'Add a README file', visible: true - click_button 'Commit changes' + project.repository.create_file( + user, + 'README.md', + 'Example readme', + message: 'Add README', + branch_name: 'master') + visit project_tags_path(project) end diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index a9cfe794177..0f8daaf8e15 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Task Lists' do +RSpec.describe 'Task Lists', :js do include Warden::Test::Helpers let_it_be(:project) { create(:project, :public, :repository) } @@ -38,41 +38,7 @@ RSpec.describe 'Task Lists' do MARKDOWN end - let(:nested_tasks_markdown) do - <<-EOT.strip_heredoc - - [ ] Task a - - [x] Task a.1 - - [ ] Task a.2 - - [ ] Task b - - 1. [ ] Task 1 - 1. [ ] Task 1.1 - 1. [x] Task 1.2 - EOT - end - - let(:commented_tasks_markdown) do - <<-EOT.strip_heredoc - <!-- - - [ ] a - --> - - - [ ] b - EOT - end - - let(:summary_no_blank_line_markdown) do - <<-EOT.strip_heredoc - <details> - <summary>No blank line after summary element breaks task list</summary> - 1. [ ] People Ops: do such and such - </details> - - * [ ] Task 1 - EOT - end - - before(:all) do + before_all do project.add_maintainer(user) project.add_guest(user2) end @@ -86,7 +52,7 @@ RSpec.describe 'Task Lists' do end describe 'for Issues' do - describe 'multiple tasks', :js do + describe 'multiple tasks' do let!(:issue) { create(:issue, description: markdown, author: user, project: project) } it 'renders' do @@ -127,7 +93,7 @@ RSpec.describe 'Task Lists' do end end - describe 'single incomplete task', :js do + describe 'single incomplete task' do let!(:issue) { create(:issue, description: singleIncompleteMarkdown, author: user, project: project) } it 'renders' do @@ -146,7 +112,7 @@ RSpec.describe 'Task Lists' do end end - describe 'single complete task', :js do + describe 'single complete task' do let!(:issue) { create(:issue, description: singleCompleteMarkdown, author: user, project: project) } it 'renders' do @@ -175,7 +141,7 @@ RSpec.describe 'Task Lists' do project: project, author: user) end - it 'renders for note body', :js do + it 'renders for note body' do visit_issue(project, issue) expect(page).to have_selector('.note ul.task-list', count: 1) @@ -183,14 +149,14 @@ RSpec.describe 'Task Lists' do expect(page).to have_selector('.note ul input[checked]', count: 2) end - it 'contains the required selectors', :js do + it 'contains the required selectors' do visit_issue(project, issue) expect(page).to have_selector('.note .js-task-list-container') expect(page).to have_selector('.note .js-task-list-container .task-list .task-list-item .task-list-item-checkbox') end - it 'is only editable by author', :js do + it 'is only editable by author' do visit_issue(project, issue) expect(page).to have_selector('.js-task-list-container') @@ -209,7 +175,7 @@ RSpec.describe 'Task Lists' do project: project, author: user) end - it 'renders for note body', :js do + it 'renders for note body' do visit_issue(project, issue) expect(page).to have_selector('.note ul.task-list', count: 1) @@ -224,7 +190,7 @@ RSpec.describe 'Task Lists' do project: project, author: user) end - it 'renders for note body', :js do + it 'renders for note body' do visit_issue(project, issue) expect(page).to have_selector('.note ul.task-list', count: 1) @@ -240,7 +206,7 @@ RSpec.describe 'Task Lists' do end shared_examples 'multiple tasks' do - it 'renders for description', :js do + it 'renders for description' do visit_merge_request(project, merge) wait_for_requests @@ -249,7 +215,7 @@ RSpec.describe 'Task Lists' do expect(page).to have_selector('ul input[checked]', count: 2) end - it 'contains the required selectors', :js do + it 'contains the required selectors' do visit_merge_request(project, merge) wait_for_requests @@ -261,7 +227,7 @@ RSpec.describe 'Task Lists' do expect(page).to have_selector('form.js-issuable-update') end - it 'is only editable by author', :js do + it 'is only editable by author' do visit_merge_request(project, merge) wait_for_requests @@ -300,7 +266,7 @@ RSpec.describe 'Task Lists' do describe 'single incomplete task' do let!(:merge) { create(:merge_request, :simple, description: singleIncompleteMarkdown, author: user, source_project: project) } - it 'renders for description', :js do + it 'renders for description' do visit_merge_request(project, merge) wait_for_requests @@ -319,7 +285,7 @@ RSpec.describe 'Task Lists' do describe 'single complete task' do let!(:merge) { create(:merge_request, :simple, description: singleCompleteMarkdown, author: user, source_project: project) } - it 'renders for description', :js do + it 'renders for description' do visit_merge_request(project, merge) wait_for_requests @@ -337,7 +303,17 @@ RSpec.describe 'Task Lists' do end describe 'markdown task edge cases' do - describe 'commented tasks', :js do + describe 'commented tasks' do + let(:commented_tasks_markdown) do + <<-EOT.strip_heredoc + <!-- + - [ ] a + --> + + - [ ] b + EOT + end + let!(:issue) { create(:issue, description: commented_tasks_markdown, author: user, project: project) } it 'renders' do @@ -360,7 +336,18 @@ RSpec.describe 'Task Lists' do end end - describe 'summary with no blank line', :js do + describe 'summary with no blank line' do + let(:summary_no_blank_line_markdown) do + <<-EOT.strip_heredoc + <details> + <summary>No blank line after summary element breaks task list</summary> + 1. [ ] People Ops: do such and such + </details> + + * [ ] Task 1 + EOT + end + let!(:issue) { create(:issue, description: summary_no_blank_line_markdown, author: user, project: project) } it 'renders' do @@ -382,5 +369,31 @@ RSpec.describe 'Task Lists' do expect(page).to have_selector('ul input[checked]', count: 1) end end + + describe 'markdown starting with new line character' do + let(:markdown_starting_with_new_line) do + <<-EOT.strip_heredoc + + - [ ] Task 1 + EOT + end + + let(:merge_request) { create(:merge_request, description: markdown_starting_with_new_line, author: user, source_project: project) } + + it 'allows the task to be checked' do + visit project_merge_request_path(project, merge_request) + wait_for_requests + + expect(page).to have_selector('ul input[checked]', count: 0) + + find('.task-list-item-checkbox').click + wait_for_requests + + visit project_merge_request_path(project, merge_request) + wait_for_requests + + expect(page).to have_selector('ul input[checked]', count: 1) + end + end end end diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb index 4be27673adf..6fa805d8c74 100644 --- a/spec/features/triggers_spec.rb +++ b/spec/features/triggers_spec.rb @@ -19,114 +19,132 @@ RSpec.describe 'Triggers', :js do visit project_settings_ci_cd_path(@project) end - describe 'create trigger workflow' do - it 'prevents adding new trigger with no description' do - fill_in 'trigger_description', with: '' - click_button 'Add trigger' - - # See if input has error due to empty value - expect(page.find('form.gl-show-field-errors .gl-field-error')).to be_visible - end + shared_examples 'triggers page' do + describe 'create trigger workflow' do + it 'prevents adding new trigger with no description' do + fill_in 'trigger_description', with: '' + click_button 'Add trigger' + + # See if input has error due to empty value + expect(page.find('form.gl-show-field-errors .gl-field-error')).to be_visible + end - it 'adds new trigger with description' do - fill_in 'trigger_description', with: 'trigger desc' - click_button 'Add trigger' + it 'adds new trigger with description' do + fill_in 'trigger_description', with: 'trigger desc' + click_button 'Add trigger' - # See if "trigger creation successful" message displayed and description and owner are correct - expect(page.find('.flash-notice')).to have_content 'Trigger was created successfully.' - expect(page.find('.triggers-list')).to have_content 'trigger desc' - expect(page.find('.triggers-list .trigger-owner')).to have_content user.name + aggregate_failures 'display creation notice and trigger is created' do + expect(page.find('.flash-notice')).to have_content 'Trigger was created successfully.' + expect(page.find('.triggers-list')).to have_content 'trigger desc' + expect(page.find('.triggers-list .trigger-owner')).to have_content user.name + end + end end - end - - describe 'edit trigger workflow' do - let(:new_trigger_title) { 'new trigger' } - it 'click on edit trigger opens edit trigger page' do - create(:ci_trigger, owner: user, project: @project, description: trigger_title) - visit project_settings_ci_cd_path(@project) + describe 'edit trigger workflow' do + let(:new_trigger_title) { 'new trigger' } - # See if edit page has correct descrption - find('a[title="Edit"]').send_keys(:return) - expect(page.find('#trigger_description').value).to have_content 'trigger desc' - end + it 'click on edit trigger opens edit trigger page' do + create(:ci_trigger, owner: user, project: @project, description: trigger_title) + visit project_settings_ci_cd_path(@project) - it 'edit trigger and save' do - create(:ci_trigger, owner: user, project: @project, description: trigger_title) - visit project_settings_ci_cd_path(@project) + # See if edit page has correct descrption + find('a[title="Edit"]').send_keys(:return) + expect(page.find('#trigger_description').value).to have_content 'trigger desc' + end - # See if edit page opens, then fill in new description and save - find('a[title="Edit"]').send_keys(:return) - fill_in 'trigger_description', with: new_trigger_title - click_button 'Save trigger' + it 'edit trigger and save' do + create(:ci_trigger, owner: user, project: @project, description: trigger_title) + visit project_settings_ci_cd_path(@project) - # See if "trigger updated successfully" message displayed and description and owner are correct - expect(page.find('.flash-notice')).to have_content 'Trigger was successfully updated.' - expect(page.find('.triggers-list')).to have_content new_trigger_title - expect(page.find('.triggers-list .trigger-owner')).to have_content user.name - end - end + # See if edit page opens, then fill in new description and save + find('a[title="Edit"]').send_keys(:return) + fill_in 'trigger_description', with: new_trigger_title + click_button 'Save trigger' - describe 'trigger "Revoke" workflow' do - before do - create(:ci_trigger, owner: user2, project: @project, description: trigger_title) - visit project_settings_ci_cd_path(@project) + aggregate_failures 'display update notice and trigger is updated' do + expect(page.find('.flash-notice')).to have_content 'Trigger was successfully updated.' + expect(page.find('.triggers-list')).to have_content new_trigger_title + expect(page.find('.triggers-list .trigger-owner')).to have_content user.name + end + end end - it 'button "Revoke" has correct alert' do - expected_alert = 'By revoking a trigger you will break any processes making use of it. Are you sure?' - expect(page.find('a.btn-trigger-revoke')['data-confirm']).to eq expected_alert - end + describe 'trigger "Revoke" workflow' do + before do + create(:ci_trigger, owner: user2, project: @project, description: trigger_title) + visit project_settings_ci_cd_path(@project) + end - it 'revoke trigger' do - # See if "Revoke" on trigger works post trigger creation - page.accept_confirm do - find('a.btn-trigger-revoke').send_keys(:return) + it 'button "Revoke" has correct alert' do + expected_alert = 'By revoking a trigger you will break any processes making use of it. Are you sure?' + expect(page.find('[data-testid="trigger_revoke_button"]')['data-confirm']).to eq expected_alert end - expect(page.find('.flash-notice')).to have_content 'Trigger removed' - expect(page).to have_selector('p.settings-message.text-center.gl-mb-3') - end - end + it 'revoke trigger' do + # See if "Revoke" on trigger works post trigger creation + page.accept_confirm do + find('[data-testid="trigger_revoke_button"]').send_keys(:return) + end - describe 'show triggers workflow' do - it 'contains trigger description placeholder' do - expect(page.find('#trigger_description')['placeholder']).to eq 'Trigger description' + aggregate_failures 'trigger is removed' do + expect(page.find('.flash-notice')).to have_content 'Trigger removed' + expect(page).to have_css('[data-testid="no_triggers_content"]') + end + end end - it 'show "invalid" badge for trigger with owner having insufficient permissions' do - create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title) - visit project_settings_ci_cd_path(@project) + describe 'show triggers workflow' do + it 'contains trigger description placeholder' do + expect(page.find('#trigger_description')['placeholder']).to eq 'Trigger description' + end - expect(page.find('.triggers-list')).to have_content 'invalid' - expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]') - end + it 'show "invalid" badge for trigger with owner having insufficient permissions' do + create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title) + visit project_settings_ci_cd_path(@project) + + aggregate_failures 'has invalid badge and no edit link' do + expect(page.find('.triggers-list')).to have_content 'invalid' + expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]') + end + end - it 'do not show "Edit" or full token for not owned trigger' do - # Create trigger with user different from current_user - create(:ci_trigger, owner: user2, project: @project, description: trigger_title) - visit project_settings_ci_cd_path(@project) + it 'do not show "Edit" or full token for not owned trigger' do + # Create trigger with user different from current_user + create(:ci_trigger, owner: user2, project: @project, description: trigger_title) + visit project_settings_ci_cd_path(@project) + + aggregate_failures 'shows truncated token, no clipboard button and no edit link' do + expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3]) + expect(page.find('.triggers-list')).not_to have_selector('[data-testid="clipboard-btn"]') + expect(page.find('.triggers-list .trigger-owner')).not_to have_content user.name + expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]') + end + end - # See if trigger not owned by current_user shows only first few token chars and doesn't have copy-to-clipboard button - expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3]) - expect(page.find('.triggers-list')).not_to have_selector('button.btn-clipboard') + it 'show "Edit" and full token for owned trigger' do + create(:ci_trigger, owner: user, project: @project, description: trigger_title) + visit project_settings_ci_cd_path(@project) - # See if trigger owner name doesn't match with current_user and trigger is non-editable - expect(page.find('.triggers-list .trigger-owner')).not_to have_content user.name - expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]') + aggregate_failures 'shows full token, clipboard button and edit link' do + expect(page.find('.triggers-list')).to have_content @project.triggers.first.token + expect(page.find('.triggers-list')).to have_selector('[data-testid="clipboard-btn"]') + expect(page.find('.triggers-list .trigger-owner')).to have_content user.name + expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]') + end + end end + end - it 'show "Edit" and full token for owned trigger' do - create(:ci_trigger, owner: user, project: @project, description: trigger_title) - visit project_settings_ci_cd_path(@project) - - # See if trigger shows full token and has copy-to-clipboard button - expect(page.find('.triggers-list')).to have_content @project.triggers.first.token - expect(page.find('.triggers-list')).to have_selector('button.btn-clipboard') + context 'when ci_pipeline_triggers_settings_vue_ui is enabled' do + it_behaves_like 'triggers page' + end - # See if trigger owner name matches with current_user and is editable - expect(page.find('.triggers-list .trigger-owner')).to have_content user.name - expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]') + context 'when ci_pipeline_triggers_settings_vue_ui is disabled' do + before do + stub_feature_flags(ci_pipeline_triggers_settings_vue_ui: false) end + + it_behaves_like 'triggers page' end end diff --git a/spec/features/users/overview_spec.rb b/spec/features/users/overview_spec.rb index 549087e5950..67216b04504 100644 --- a/spec/features/users/overview_spec.rb +++ b/spec/features/users/overview_spec.rb @@ -21,15 +21,15 @@ RSpec.describe 'Overview tab on a user profile', :js do sign_in user end - describe 'activities section' do - shared_context 'visit overview tab' do - before do - visit user.username - page.find('.js-overview-tab a').click - wait_for_requests - end + shared_context 'visit overview tab' do + before do + visit user.username + page.find('.js-overview-tab a').click + wait_for_requests end + end + describe 'activities section' do describe 'user has no activities' do include_context 'visit overview tab' @@ -84,14 +84,6 @@ RSpec.describe 'Overview tab on a user profile', :js do end describe 'projects section' do - shared_context 'visit overview tab' do - before do - visit user.username - page.find('.js-overview-tab a').click - wait_for_requests - end - end - describe 'user has no personal projects' do include_context 'visit overview tab' @@ -158,4 +150,52 @@ RSpec.describe 'Overview tab on a user profile', :js do end end end + + describe 'bot user' do + let(:bot_user) { create(:user, user_type: :security_bot) } + + shared_context "visit bot's overview tab" do + before do + visit bot_user.username + page.find('.js-overview-tab a').click + wait_for_requests + end + end + + describe 'feature flag enabled' do + before do + stub_feature_flags(security_auto_fix: true) + end + + include_context "visit bot's overview tab" + + it "activity panel's title is 'Bot activity'" do + page.within('.activities-block') do + expect(page).to have_text('Bot activity') + end + end + + it 'does not show projects panel' do + expect(page).not_to have_selector('.projects-block') + end + end + + describe 'feature flag disabled' do + before do + stub_feature_flags(security_auto_fix: false) + end + + include_context "visit bot's overview tab" + + it "activity panel's title is not 'Bot activity'" do + page.within('.activities-block') do + expect(page).not_to have_text('Bot activity') + end + end + + it 'shows projects panel' do + expect(page).to have_selector('.projects-block') + end + end + end end diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb index dd5c2442d00..466b7361da9 100644 --- a/spec/features/users/show_spec.rb +++ b/spec/features/users/show_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe 'User page' do include ExternalAuthorizationServiceHelpers - let(:user) { create(:user) } + let(:user) { create(:user, bio: '**Lorem** _ipsum_ dolor sit [amet](https://example.com)') } context 'with public profile' do it 'shows all the tabs' do @@ -174,4 +174,54 @@ RSpec.describe 'User page' do end end end + + context 'page description' do + before do + visit(user_path(user)) + end + + it_behaves_like 'page meta description', 'Lorem ipsum dolor sit amet' + end + + context 'with a bot user' do + let(:user) { create(:user, user_type: :security_bot) } + + describe 'feature flag enabled' do + before do + stub_feature_flags(security_auto_fix: true) + end + + it 'only shows Overview and Activity tabs' do + visit(user_path(user)) + + page.within '.nav-links' do + expect(page).to have_link('Overview') + expect(page).to have_link('Activity') + expect(page).not_to have_link('Groups') + expect(page).not_to have_link('Contributed projects') + expect(page).not_to have_link('Personal projects') + expect(page).not_to have_link('Snippets') + end + end + end + + describe 'feature flag disabled' do + before do + stub_feature_flags(security_auto_fix: false) + end + + it 'only shows Overview and Activity tabs' do + visit(user_path(user)) + + page.within '.nav-links' do + expect(page).to have_link('Overview') + expect(page).to have_link('Activity') + expect(page).to have_link('Groups') + expect(page).to have_link('Contributed projects') + expect(page).to have_link('Personal projects') + expect(page).to have_link('Snippets') + end + end + end + end end diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb index 5fd0e677cd0..c59121626f0 100644 --- a/spec/features/users/signup_spec.rb +++ b/spec/features/users/signup_spec.rb @@ -7,6 +7,14 @@ RSpec.shared_examples 'Signup' do let(:new_user) { build_stubbed(:user) } + def fill_in_signup_form + fill_in 'new_user_username', with: new_user.username + fill_in 'new_user_email', with: new_user.email + 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_password', with: new_user.password + end + describe 'username validation', :js do before do visit new_user_registration_path @@ -144,20 +152,9 @@ RSpec.shared_examples 'Signup' do it 'creates the user account and sends a confirmation email' do visit new_user_registration_path - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: new_user.email - - if Gitlab::Experimentation.enabled?(:signup_flow) - fill_in 'new_user_first_name', with: new_user.first_name - fill_in 'new_user_last_name', with: new_user.last_name - else - fill_in 'new_user_name', with: new_user.name - end - - fill_in 'new_user_password', with: new_user.password + fill_in_signup_form 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') end @@ -171,46 +168,14 @@ RSpec.shared_examples 'Signup' do it 'creates the user account and sends a confirmation email' do visit new_user_registration_path - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: new_user.email - - if Gitlab::Experimentation.enabled?(:signup_flow) - fill_in 'new_user_first_name', with: new_user.first_name - fill_in 'new_user_last_name', with: new_user.last_name - else - fill_in 'new_user_name', with: new_user.name - end - - fill_in 'new_user_password', with: new_user.password + fill_in_signup_form expect { click_button 'Register' }.to change { User.count }.by(1) - expect(current_path).to eq users_sign_up_welcome_path end end end - context "when sigining up with different cased emails" do - it "creates the user successfully" do - visit new_user_registration_path - - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: new_user.email - - if Gitlab::Experimentation.enabled?(:signup_flow) - fill_in 'new_user_first_name', with: new_user.first_name - fill_in 'new_user_last_name', with: new_user.last_name - else - fill_in 'new_user_name', with: new_user.name - end - - fill_in 'new_user_password', with: new_user.password - click_button "Register" - - expect(current_path).to eq users_sign_up_welcome_path - end - end - context "when not sending confirmation email" do before do stub_application_setting(send_user_confirmation_email: false) @@ -219,17 +184,7 @@ RSpec.shared_examples 'Signup' do it 'creates the user account and goes to dashboard' do visit new_user_registration_path - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: new_user.email - - if Gitlab::Experimentation.enabled?(:signup_flow) - fill_in 'new_user_first_name', with: new_user.first_name - fill_in 'new_user_last_name', with: new_user.last_name - else - fill_in 'new_user_name', with: new_user.name - end - - fill_in 'new_user_password', with: new_user.password + fill_in_signup_form click_button "Register" expect(current_path).to eq users_sign_up_welcome_path @@ -239,20 +194,10 @@ RSpec.shared_examples 'Signup' do context 'with errors' do it "displays the errors" do - existing_user = create(:user) - + create(:user, email: new_user.email) visit new_user_registration_path - if Gitlab::Experimentation.enabled?(:signup_flow) - fill_in 'new_user_first_name', with: new_user.first_name - fill_in 'new_user_last_name', with: new_user.last_name - else - fill_in 'new_user_name', with: new_user.name - end - - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: existing_user.email - fill_in 'new_user_password', with: new_user.password + fill_in_signup_form click_button "Register" expect(current_path).to eq user_registration_path @@ -261,20 +206,10 @@ RSpec.shared_examples 'Signup' do end it 'does not redisplay the password' do - existing_user = create(:user) - + create(:user, email: new_user.email) visit new_user_registration_path - if Gitlab::Experimentation.enabled?(:signup_flow) - fill_in 'new_user_first_name', with: new_user.first_name - fill_in 'new_user_last_name', with: new_user.last_name - else - fill_in 'new_user_name', with: new_user.name - end - - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: existing_user.email - fill_in 'new_user_password', with: new_user.password + fill_in_signup_form click_button "Register" expect(current_path).to eq user_registration_path @@ -287,45 +222,14 @@ RSpec.shared_examples 'Signup' do enforce_terms end - it 'requires the user to check the checkbox' do + it 'renders text that the user confirms terms by clicking register' do visit new_user_registration_path - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: new_user.email - - if Gitlab::Experimentation.enabled?(:signup_flow) - fill_in 'new_user_first_name', with: new_user.first_name - fill_in 'new_user_last_name', with: new_user.last_name - else - fill_in 'new_user_name', with: new_user.name - end - - fill_in 'new_user_password', with: new_user.password + expect(page).to have_content(/By clicking Register, I agree that I have read and accepted the Terms of Use and Privacy Policy/) + fill_in_signup_form click_button 'Register' - expect(current_path).to eq new_user_session_path - expect(page).to have_content(/you must accept our terms of service/i) - end - - it 'asks the user to accept terms before going to the dashboard' do - visit new_user_registration_path - - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: new_user.email - - if Gitlab::Experimentation.enabled?(:signup_flow) - fill_in 'new_user_first_name', with: new_user.first_name - fill_in 'new_user_last_name', with: new_user.last_name - else - fill_in 'new_user_name', with: new_user.name - end - - fill_in 'new_user_password', with: new_user.password - check :terms_opt_in - - click_button "Register" - expect(current_path).to eq users_sign_up_welcome_path end end @@ -353,17 +257,7 @@ RSpec.shared_examples 'Signup' do it 'prevents from signing up' do visit new_user_registration_path - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: new_user.email - - if Gitlab::Experimentation.enabled?(:signup_flow) - fill_in 'new_user_first_name', with: new_user.first_name - fill_in 'new_user_last_name', with: new_user.last_name - else - fill_in 'new_user_name', with: new_user.name - end - - fill_in 'new_user_password', with: new_user.password + fill_in_signup_form expect { click_button 'Register' }.not_to change { User.count } expect(page).to have_content('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.') @@ -374,17 +268,7 @@ RSpec.shared_examples 'Signup' do it 'prevents from signing up' do visit new_user_registration_path - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: new_user.email - - if Gitlab::Experimentation.enabled?(:signup_flow) - fill_in 'new_user_first_name', with: new_user.first_name - fill_in 'new_user_last_name', with: new_user.last_name - else - fill_in 'new_user_name', with: new_user.name - end - - fill_in 'new_user_password', with: new_user.password + fill_in_signup_form expect { click_button 'Register' }.not_to change { User.count } expect(page).to have_content('That was a bit too quick! Please resubmit.') @@ -393,36 +277,27 @@ RSpec.shared_examples 'Signup' do end it 'redirects to step 2 of the signup process, sets the role and redirects back' do - new_user = build_stubbed(:user) visit new_user_registration_path - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: new_user.email - - if Gitlab::Experimentation.enabled?(:signup_flow) - fill_in 'new_user_first_name', with: new_user.first_name - fill_in 'new_user_last_name', with: new_user.last_name - else - fill_in 'new_user_name', with: new_user.name - end - - fill_in 'new_user_password', with: new_user.password + fill_in_signup_form click_button 'Register' + visit new_project_path expect(page).to have_current_path(users_sign_up_welcome_path) select 'Software Developer', from: 'user_role' click_button 'Get started!' - new_user = User.find_by_username(new_user.username) - expect(new_user.software_developer_role?).to be_truthy - expect(new_user.setup_for_company).to be_nil + created_user = User.find_by_username(new_user.username) + + expect(created_user.software_developer_role?).to be_truthy + expect(created_user.setup_for_company).to be_nil expect(page).to have_current_path(new_project_path) end end -RSpec.shared_examples 'Signup name validation' do |field, max_length| +RSpec.shared_examples 'Signup name validation' do |field, max_length, label| before do visit new_user_registration_path end @@ -446,10 +321,10 @@ RSpec.shared_examples 'Signup name validation' do |field, max_length| expect(find('.name')).to have_css '.gl-field-error-outline' end - it "shows an error message if the user\'s fullname is longer than #{max_length} characters" do + it "shows an error message if the user\'s #{label} is longer than #{max_length} characters" do fill_in field, with: 'n' * (max_length + 1) - expect(page).to have_content("Name is too long (maximum is #{max_length} characters).") + expect(page).to have_content("#{label} is too long (maximum is #{max_length} characters).") end it 'shows an error message if the username contains emojis' do @@ -467,7 +342,8 @@ RSpec.describe 'With original flow' do end it_behaves_like 'Signup' - it_behaves_like 'Signup name validation', 'new_user_name', 255 + it_behaves_like 'Signup name validation', 'new_user_first_name', 127, 'First name' + it_behaves_like 'Signup name validation', 'new_user_last_name', 127, 'Last name' end RSpec.describe 'With experimental flow' do @@ -477,30 +353,6 @@ RSpec.describe 'With experimental flow' do end it_behaves_like 'Signup' - it_behaves_like 'Signup name validation', 'new_user_first_name', 127 - it_behaves_like 'Signup name validation', 'new_user_last_name', 127 - - context 'when terms_opt_in experimental is enabled' do - include TermsHelper - - before do - enforce_terms - stub_experiment(signup_flow: true, terms_opt_in: true) - stub_experiment_for_user(signup_flow: true, terms_opt_in: true) - end - - it 'terms are checked by default' do - new_user = build_stubbed(:user) - - visit new_user_registration_path - 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' - - expect(current_path).to eq users_sign_up_welcome_path - end - end + it_behaves_like 'Signup name validation', 'new_user_first_name', 127, 'First name' + it_behaves_like 'Signup name validation', 'new_user_last_name', 127, 'Last name' end diff --git a/spec/features/users/terms_spec.rb b/spec/features/users/terms_spec.rb index 5275845fe5b..7500f2fe59a 100644 --- a/spec/features/users/terms_spec.rb +++ b/spec/features/users/terms_spec.rb @@ -26,6 +26,21 @@ RSpec.describe 'Users > Terms' do expect(page).not_to have_content('Continue') end + context 'when user is a project bot' do + let(:project_bot) { create(:user, :project_bot) } + + before do + enforce_terms + end + + it 'auto accepts the terms' do + visit terms_path + + expect(page).not_to have_content('Accept terms') + expect(project_bot.terms_accepted?).to be(true) + end + end + context 'when signed in' do let(:user) { create(:user) } |