diff options
Diffstat (limited to 'spec/features/projects')
43 files changed, 1885 insertions, 1062 deletions
diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb index dcad7ee66a3..4bfe8852291 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| - travel_to((threshold + i).ago) { create_file(message: "a commit in stale-#{i}", branch_name: "stale-#{i}") } + travel_to((threshold + i.hours).ago) { create_file(message: "a commit in stale-#{i}", branch_name: "stale-#{i}") } end # Add 6 active branches (1..6).each do |i| - travel_to((threshold - i).ago) { create_file(message: "a commit in active-#{i}", branch_name: "active-#{i}") } + travel_to((threshold - i.hours).ago) { create_file(message: "a commit in active-#{i}", branch_name: "active-#{i}") } end end @@ -34,7 +34,7 @@ RSpec.describe 'Branches' do visit project_branches_path(project) expect(page).to have_content(sorted_branches(repository, count: 5, sort_by: :updated_desc, state: 'active')) - expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_desc, state: 'stale')) + expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_asc, state: 'stale')) expect(page).to have_link('Show more active branches', href: project_branches_filtered_path(project, state: 'active')) expect(page).not_to have_content('Show more stale branches') @@ -50,10 +50,10 @@ RSpec.describe 'Branches' do end describe 'Stale branches page' do - it 'shows 4 active branches sorted by last updated' do + it 'shows 4 stale branches sorted by last updated' do visit project_branches_filtered_path(project, state: 'stale') - expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_desc, state: 'stale')) + expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_asc, state: 'stale')) end end diff --git a/spec/features/projects/commit/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb index 9fe3f4cd63e..489a90cc8fc 100644 --- a/spec/features/projects/commit/cherry_pick_spec.rb +++ b/spec/features/projects/commit/cherry_pick_spec.rb @@ -2,108 +2,126 @@ require 'spec_helper' -RSpec.describe 'Cherry-pick Commits' do - let(:user) { create(:user) } - let(:group) { create(:group) } - let(:project) { create(:project, :repository, namespace: group) } - let(:master_pickable_commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') } - let(:master_pickable_merge) { project.commit('e56497bb5f03a90a51293fc6d516788730953899') } +RSpec.describe 'Cherry-pick Commits', :js do + let_it_be(:user) { create(:user) } + let_it_be(:sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' } + let!(:project) { create_default(:project, :repository, namespace: user.namespace) } + let(:master_pickable_commit) { project.commit(sha) } before do sign_in(user) - project.add_maintainer(user) - visit project_commit_path(project, master_pickable_commit.id) end - context "I cherry-pick a commit" do - it do - find("a[href='#modal-cherry-pick-commit']").click - expect(page).not_to have_content('v1.0.0') # Only branches, not tags - page.within('#modal-cherry-pick-commit') do - uncheck 'create_merge_request' - click_button 'Cherry-pick' - end - expect(page).to have_content('The commit has been successfully cherry-picked into master.') - end - end + context 'when clicking cherry-pick from the dropdown for a commit on pipelines tab' do + it 'launches the modal form' do + create(:ci_empty_pipeline, sha: sha) + visit project_commit_path(project, master_pickable_commit.id) + click_link 'Pipelines' - context "I cherry-pick a merge commit" do - it do - find("a[href='#modal-cherry-pick-commit']").click - page.within('#modal-cherry-pick-commit') do - uncheck 'create_merge_request' - click_button 'Cherry-pick' + open_modal + + page.within(modal_selector) do + expect(page).to have_content('Cherry-pick this commit') end - expect(page).to have_content('The commit has been successfully cherry-picked into master.') end end - context "I cherry-pick a commit that was previously cherry-picked" do - it do - find("a[href='#modal-cherry-pick-commit']").click - page.within('#modal-cherry-pick-commit') do - uncheck 'create_merge_request' - click_button 'Cherry-pick' - end + context 'when starting from the commit tab' do + before do visit project_commit_path(project, master_pickable_commit.id) - find("a[href='#modal-cherry-pick-commit']").click - page.within('#modal-cherry-pick-commit') do - uncheck 'create_merge_request' - click_button 'Cherry-pick' - end - expect(page).to have_content('Sorry, we cannot cherry-pick this commit automatically.') end - end - context "I cherry-pick a commit in a new merge request", :js do - it do - find('.header-action-buttons a.dropdown-toggle').click - find("a[href='#modal-cherry-pick-commit']").click - page.within('#modal-cherry-pick-commit') do - click_button 'Cherry-pick' + context 'when cherry-picking a commit' do + specify do + cherry_pick_commit + + expect(page).to have_content('The commit has been successfully cherry-picked into master.') end + end - wait_for_requests + context 'when cherry-picking a merge commit' do + specify do + cherry_pick_commit - expect(page).to have_content("The commit has been successfully cherry-picked into cherry-pick-#{master_pickable_commit.short_id}. You can now submit a merge request to get this change into the original branch.") - expect(page).to have_content("From cherry-pick-#{master_pickable_commit.short_id} into master") + expect(page).to have_content('The commit has been successfully cherry-picked into master.') + end end - end - context "I cherry-pick a commit from a different branch", :js do - it do - find('.header-action-buttons a.dropdown-toggle').click - find(:css, "a[href='#modal-cherry-pick-commit']").click + context 'when cherry-picking a commit that was previously cherry-picked' do + specify do + cherry_pick_commit - page.within('#modal-cherry-pick-commit') do - click_button 'master' + visit project_commit_path(project, master_pickable_commit.id) + + cherry_pick_commit + + expect(page).to have_content('Sorry, we cannot cherry-pick this commit automatically.') end + end - wait_for_requests + context 'when cherry-picking a commit in a new merge request' do + specify do + cherry_pick_commit(create_merge_request: true) - page.within('#modal-cherry-pick-commit .dropdown-menu') do - find('.dropdown-input input').set('feature') - wait_for_requests - click_link "feature" + expect(page).to have_content("The commit has been successfully cherry-picked into cherry-pick-#{master_pickable_commit.short_id}. You can now submit a merge request to get this change into the original branch.") + expect(page).to have_content("From cherry-pick-#{master_pickable_commit.short_id} into master") end + end - page.within('#modal-cherry-pick-commit') do - uncheck 'create_merge_request' - click_button 'Cherry-pick' + context 'when I cherry-picking a commit from a different branch' do + specify do + open_modal + + page.within(modal_selector) do + click_button 'master' + end + + page.within("#{modal_selector} .dropdown-menu") do + find('[data-testid="dropdown-search-box"]').set('feature') + wait_for_requests + click_button 'feature' + end + + submit_cherry_pick + + expect(page).to have_content('The commit has been successfully cherry-picked into feature.') end + end + + context 'when the project is archived' do + let(:project) { create(:project, :repository, :archived, namespace: user.namespace) } - expect(page).to have_content('The commit has been successfully cherry-picked into feature.') + it 'does not show the cherry-pick link' do + open_dropdown + + expect(page).not_to have_text("Cherry-pick") + end end end - context 'when the project is archived' do - let(:project) { create(:project, :repository, :archived, namespace: group) } + def cherry_pick_commit(create_merge_request: false) + open_modal - it 'does not show the cherry-pick link' do - find('.header-action-buttons a.dropdown-toggle').click + submit_cherry_pick(create_merge_request: create_merge_request) + end + + def open_dropdown + find('.header-action-buttons .dropdown').click + end - expect(page).not_to have_text("Cherry-pick") - expect(page).not_to have_css("a[href='#modal-cherry-pick-commit']") + def open_modal + open_dropdown + find('[data-testid="cherry-pick-commit-link"]').click + end + + def submit_cherry_pick(create_merge_request: false) + page.within(modal_selector) do + uncheck('create_merge_request') unless create_merge_request + click_button('Cherry-pick') end end + + def modal_selector + '[data-testid="modal-commit"]' + end end diff --git a/spec/features/projects/commit/user_reverts_commit_spec.rb b/spec/features/projects/commit/user_reverts_commit_spec.rb index f3c364dab97..72c639a027e 100644 --- a/spec/features/projects/commit/user_reverts_commit_spec.rb +++ b/spec/features/projects/commit/user_reverts_commit_spec.rb @@ -6,58 +6,89 @@ RSpec.describe 'User reverts a commit', :js do include RepoHelpers let_it_be(:user) { create(:user) } - let(:project) { create(:project, :repository, namespace: user.namespace) } + let!(:project) { create_default(:project, :repository, namespace: user.namespace) } before do sign_in(user) - - visit(project_commit_path(project, sample_commit.id)) end - def revert_commit(create_merge_request: false) - find('.header-action-buttons .dropdown').click - find('[data-testid="revert-commit-link"]').click + context 'when clicking revert from the dropdown for a commit on pipelines tab' do + it 'launches the modal and is able to submit the revert' do + sha = '7d3b0f7cff5f37573aea97cebfd5692ea1689924' + create(:ci_empty_pipeline, sha: sha) + visit project_commit_path(project, project.commit(sha).id) + click_link 'Pipelines' - page.within('[data-testid="modal-commit"]') do - uncheck('create_merge_request') unless create_merge_request - click_button('Revert') + open_modal + + page.within(modal_selector) do + expect(page).to have_content('Revert this commit') + end end end - context 'without creating a new merge request' do - it 'reverts a commit' do - revert_commit + context 'when starting from the commit tab' do + before do + visit project_commit_path(project, sample_commit.id) + end + + context 'without creating a new merge request' do + it 'reverts a commit' do + revert_commit + + expect(page).to have_content('The commit has been successfully reverted.') + end + + it 'does not revert a previously reverted commit' do + revert_commit + # Visit the comment again once it was reverted. + visit project_commit_path(project, sample_commit.id) + + revert_commit - expect(page).to have_content('The commit has been successfully reverted.') + expect(page).to have_content('Sorry, we cannot revert this commit automatically.') + end end - it 'does not revert a previously reverted commit' do - revert_commit - # Visit the comment again once it was reverted. - visit project_commit_path(project, sample_commit.id) + context 'with creating a new merge request' do + it 'reverts a commit' do + revert_commit(create_merge_request: true) + + expect(page).to have_content('The commit has been successfully reverted. You can now submit a merge request to get this change into the original branch.') + expect(page).to have_content("From revert-#{Commit.truncate_sha(sample_commit.id)} into master") + end + end - revert_commit + context 'when the project is archived' do + let(:project) { create(:project, :repository, :archived, namespace: user.namespace) } - expect(page).to have_content('Sorry, we cannot revert this commit automatically.') + it 'does not show the revert link' do + open_dropdown + + expect(page).not_to have_link('Revert') + end end end - context 'with creating a new merge request' do - it 'reverts a commit' do - revert_commit(create_merge_request: true) + def revert_commit(create_merge_request: false) + open_modal - expect(page).to have_content('The commit has been successfully reverted. You can now submit a merge request to get this change into the original branch.') - expect(page).to have_content("From revert-#{Commit.truncate_sha(sample_commit.id)} into master") + page.within(modal_selector) do + uncheck('create_merge_request') unless create_merge_request + click_button('Revert') end end - context 'when the project is archived' do - let(:project) { create(:project, :repository, :archived, namespace: user.namespace) } + def open_dropdown + find('.header-action-buttons .dropdown').click + end - it 'does not show the revert link' do - find('.header-action-buttons .dropdown').click + def open_modal + open_dropdown + find('[data-testid="revert-commit-link"]').click + end - expect(page).not_to have_link('Revert') - end + def modal_selector + '[data-testid="modal-commit"]' end end diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb index 596b4773716..4894e2b7f3e 100644 --- a/spec/features/projects/commits/user_browses_commits_spec.rb +++ b/spec/features/projects/commits/user_browses_commits_spec.rb @@ -203,10 +203,11 @@ RSpec.describe 'User browses commits' do context 'when click the compare tab' do before do + wait_for_requests click_link('Compare') end - it 'does not render create merge request button' do + it 'does not render create merge request button', :js do expect(page).not_to have_link 'Create merge request' end end @@ -236,10 +237,11 @@ RSpec.describe 'User browses commits' do context 'when click the compare tab' do before do + wait_for_requests click_link('Compare') end - it 'renders create merge request button' do + it 'renders create merge request button', :js do expect(page).to have_link 'Create merge request' end end @@ -276,10 +278,11 @@ RSpec.describe 'User browses commits' do context 'when click the compare tab' do before do + wait_for_requests click_link('Compare') end - it 'renders button to the merge request' do + it 'renders button to the merge request', :js do expect(page).not_to have_link 'Create merge request' expect(page).to have_link 'View open merge request', href: project_merge_request_path(project, merge_request) end diff --git a/spec/features/projects/compare_spec.rb b/spec/features/projects/compare_spec.rb index e387ea4d473..64e9968061c 100644 --- a/spec/features/projects/compare_spec.rb +++ b/spec/features/projects/compare_spec.rb @@ -17,10 +17,10 @@ RSpec.describe "Compare", :js do visit project_compare_index_path(project, from: 'master', to: 'master') select_using_dropdown 'from', 'feature' - expect(find('.js-compare-from-dropdown .dropdown-toggle-text')).to have_content('feature') + expect(find('.js-compare-from-dropdown .gl-new-dropdown-button-text')).to have_content('feature') select_using_dropdown 'to', 'binary-encoding' - expect(find('.js-compare-to-dropdown .dropdown-toggle-text')).to have_content('binary-encoding') + expect(find('.js-compare-to-dropdown .gl-new-dropdown-button-text')).to have_content('binary-encoding') click_button 'Compare' @@ -32,8 +32,8 @@ RSpec.describe "Compare", :js do it "pre-populates fields" do visit project_compare_index_path(project, from: "master", to: "master") - expect(find(".js-compare-from-dropdown .dropdown-toggle-text")).to have_content("master") - expect(find(".js-compare-to-dropdown .dropdown-toggle-text")).to have_content("master") + expect(find(".js-compare-from-dropdown .gl-new-dropdown-button-text")).to have_content("master") + expect(find(".js-compare-to-dropdown .gl-new-dropdown-button-text")).to have_content("master") end it_behaves_like 'compares branches' @@ -99,7 +99,7 @@ RSpec.describe "Compare", :js do find(".js-compare-from-dropdown .compare-dropdown-toggle").click - expect(find(".js-compare-from-dropdown .dropdown-content")).to have_selector("li", count: 3) + expect(find(".js-compare-from-dropdown .gl-new-dropdown-contents")).to have_selector('li.gl-new-dropdown-item', count: 1) end context 'when commit has overflow', :js do @@ -125,10 +125,10 @@ RSpec.describe "Compare", :js do visit project_compare_index_path(project, from: "master", to: "master") select_using_dropdown "from", "v1.0.0" - expect(find(".js-compare-from-dropdown .dropdown-toggle-text")).to have_content("v1.0.0") + expect(find(".js-compare-from-dropdown .gl-new-dropdown-button-text")).to have_content("v1.0.0") select_using_dropdown "to", "v1.1.0" - expect(find(".js-compare-to-dropdown .dropdown-toggle-text")).to have_content("v1.1.0") + expect(find(".js-compare-to-dropdown .gl-new-dropdown-button-text")).to have_content("v1.1.0") click_button "Compare" expect(page).to have_content "Commits" @@ -136,19 +136,22 @@ RSpec.describe "Compare", :js do end def select_using_dropdown(dropdown_type, selection, commit: false) + wait_for_requests + dropdown = find(".js-compare-#{dropdown_type}-dropdown") dropdown.find(".compare-dropdown-toggle").click # find input before using to wait for the inputs visibility dropdown.find('.dropdown-menu') dropdown.fill_in("Filter by Git revision", with: selection) + wait_for_requests if commit - dropdown.find('input[type="search"]').send_keys(:return) + dropdown.find('.gl-search-box-by-type-input').send_keys(:return) else # find before all to wait for the items visibility - dropdown.find("a[data-ref=\"#{selection}\"]", match: :first) - dropdown.all("a[data-ref=\"#{selection}\"]").last.click + dropdown.find(".js-compare-#{dropdown_type}-dropdown .dropdown-item", text: selection, match: :first) + dropdown.all(".js-compare-#{dropdown_type}-dropdown .dropdown-item", text: selection).first.click end end end diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb index 2f0fbd29cb5..c94247f65d2 100644 --- a/spec/features/projects/features_visibility_spec.rb +++ b/spec/features/projects/features_visibility_spec.rb @@ -187,7 +187,7 @@ RSpec.describe 'Edit Project Settings' do click_button "Save changes" end - expect(find(".sharing-permissions")).to have_selector(".project-feature-toggle.is-disabled", count: 4) + expect(find(".sharing-permissions")).to have_selector(".gl-toggle.is-disabled", minimum: 4) end it "shows empty features project homepage" do @@ -282,10 +282,10 @@ RSpec.describe 'Edit Project Settings' do end def toggle_feature_off(feature_name) - find(".project-feature-controls[data-for=\"#{feature_name}\"] .project-feature-toggle.is-checked").click + find(".project-feature-controls[data-for=\"#{feature_name}\"] .gl-toggle.is-checked").click end def toggle_feature_on(feature_name) - find(".project-feature-controls[data-for=\"#{feature_name}\"] .project-feature-toggle:not(.is-checked)").click + find(".project-feature-controls[data-for=\"#{feature_name}\"] .gl-toggle:not(.is-checked)").click end end diff --git a/spec/features/projects/files/dockerfile_dropdown_spec.rb b/spec/features/projects/files/dockerfile_dropdown_spec.rb index 17258f7042f..40d19a94b42 100644 --- a/spec/features/projects/files/dockerfile_dropdown_spec.rb +++ b/spec/features/projects/files/dockerfile_dropdown_spec.rb @@ -2,14 +2,16 @@ require 'spec_helper' -RSpec.describe 'Projects > Files > User wants to add a Dockerfile file', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/297400' do +RSpec.describe 'Projects > Files > User wants to add a Dockerfile file', :js do + include Spec::Support::Helpers::Features::EditorLiteSpecHelpers + before do project = create(:project, :repository) sign_in project.owner visit project_new_blob_path(project, 'master', file_name: 'Dockerfile') end - it 'user can pick a Dockerfile file from the dropdown', :js do + it 'user can pick a Dockerfile file from the dropdown' do expect(page).to have_css('.dockerfile-selector') find('.js-dockerfile-selector').click @@ -24,6 +26,6 @@ RSpec.describe 'Projects > Files > User wants to add a Dockerfile file', quarant wait_for_requests expect(page).to have_css('.dockerfile-selector .dropdown-toggle-text', text: 'Apply a template') - expect(page).to have_content('COPY ./ /usr/local/apache2/htdocs/') + expect(editor_get_value).to have_content('COPY ./ /usr/local/apache2/htdocs/') end end diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb index 5a39f2bcd98..a9f2463ecf6 100644 --- a/spec/features/projects/files/gitignore_dropdown_spec.rb +++ b/spec/features/projects/files/gitignore_dropdown_spec.rb @@ -2,14 +2,16 @@ require 'spec_helper' -RSpec.describe 'Projects > Files > User wants to add a .gitignore file' do +RSpec.describe 'Projects > Files > User wants to add a .gitignore file', :js do + include Spec::Support::Helpers::Features::EditorLiteSpecHelpers + before do project = create(:project, :repository) sign_in project.owner visit project_new_blob_path(project, 'master', file_name: '.gitignore') end - it 'user can pick a .gitignore file from the dropdown', :js do + it 'user can pick a .gitignore file from the dropdown' do expect(page).to have_css('.gitignore-selector') find('.js-gitignore-selector').click @@ -24,7 +26,7 @@ RSpec.describe 'Projects > Files > User wants to add a .gitignore file' do wait_for_requests expect(page).to have_css('.gitignore-selector .dropdown-toggle-text', text: 'Apply a template') - expect(page).to have_content('/.bundle') - expect(page).to have_content('config/initializers/secret_token.rb') + expect(editor_get_value).to have_content('/.bundle') + expect(editor_get_value).to have_content('config/initializers/secret_token.rb') end end diff --git a/spec/features/projects/files/gitlab_ci_syntax_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_syntax_yml_dropdown_spec.rb index 6308acb41f5..ca6f03472dd 100644 --- a/spec/features/projects/files/gitlab_ci_syntax_yml_dropdown_spec.rb +++ b/spec/features/projects/files/gitlab_ci_syntax_yml_dropdown_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Projects > Files > User wants to add a .gitlab-ci.yml file' do + include Spec::Support::Helpers::Features::EditorLiteSpecHelpers + before do project = create(:project, :repository) sign_in project.owner @@ -34,8 +36,7 @@ RSpec.describe 'Projects > Files > User wants to add a .gitlab-ci.yml file' do let(:experiment_active) { true } let(:in_experiment_group) { true } - it 'allows the user to pick a "Learn CI/CD syntax" template from the dropdown', :js, - { quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/297347' } } do + it 'allows the user to pick a "Learn CI/CD syntax" template from the dropdown', :js do expect(page).to have_css('.gitlab-ci-syntax-yml-selector') find('.js-gitlab-ci-syntax-yml-selector').click @@ -50,7 +51,7 @@ RSpec.describe 'Projects > Files > User wants to add a .gitlab-ci.yml file' do wait_for_requests expect(page).to have_css('.gitlab-ci-syntax-yml-selector .dropdown-toggle-text', text: 'Learn CI/CD syntax') - expect(page).to have_content('You can use artifacts to pass data to jobs in later stages.') + expect(editor_get_value).to have_content('You can use artifacts to pass data to jobs in later stages.') end end end diff --git a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb index 879cb6a65c8..55b9f38d8e7 100644 --- a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb +++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb @@ -2,14 +2,16 @@ require 'spec_helper' -RSpec.describe 'Projects > Files > User wants to add a .gitlab-ci.yml file' do +RSpec.describe 'Projects > Files > User wants to add a .gitlab-ci.yml file', :js do + include Spec::Support::Helpers::Features::EditorLiteSpecHelpers + before do project = create(:project, :repository) sign_in project.owner visit project_new_blob_path(project, 'master', file_name: '.gitlab-ci.yml') end - it 'user can pick a template from the dropdown', :js do + it 'user can pick a template from the dropdown' do expect(page).to have_css('.gitlab-ci-yml-selector') find('.js-gitlab-ci-yml-selector').click @@ -24,7 +26,7 @@ RSpec.describe 'Projects > Files > User wants to add a .gitlab-ci.yml file' do wait_for_requests expect(page).to have_css('.gitlab-ci-yml-selector .dropdown-toggle-text', text: 'Apply a template') - expect(page).to have_content('This file is a template, and might need editing before it works on your project') - expect(page).to have_content('jekyll build -d test') + expect(editor_get_value).to have_content('This file is a template, and might need editing before it works on your project') + expect(editor_get_value).to have_content('jekyll build -d test') end end diff --git a/spec/features/projects/fork_spec.rb b/spec/features/projects/fork_spec.rb index 1e84d1552a1..8d0500f5e13 100644 --- a/spec/features/projects/fork_spec.rb +++ b/spec/features/projects/fork_spec.rb @@ -9,22 +9,45 @@ RSpec.describe 'Project fork' do let(:project) { create(:project, :public, :repository) } before do - sign_in user + sign_in(user) end - it 'allows user to fork project' do + it 'allows user to fork project from the project page' do visit project_path(project) - expect(page).not_to have_css('a.disabled', text: 'Select') + expect(page).not_to have_css('a.disabled', text: 'Fork') end - it 'disables fork button when user has exceeded project limit' do - user.projects_limit = 0 - user.save! + context 'user has exceeded personal project limit' do + before do + user.update!(projects_limit: 0) + end - visit project_path(project) + it 'disables fork button on project page' do + visit project_path(project) + + expect(page).to have_css('a.disabled', text: 'Fork') + end + + context 'with a group to fork to' do + let!(:group) { create(:group).tap { |group| group.add_owner(user) } } + + it 'enables fork button on project page' do + visit project_path(project) + + expect(page).not_to have_css('a.disabled', text: 'Fork') + end + + it 'allows user to fork only to the group on fork page', :js do + visit new_project_fork_path(project) + + to_personal_namespace = find('[data-qa-selector=fork_namespace_button].disabled') + to_group = find(".fork-groups button[data-qa-name=#{group.name}]") - expect(page).to have_css('a.disabled', text: 'Fork') + expect(to_personal_namespace).not_to be_nil + expect(to_group).not_to be_disabled + end + end end context 'forking enabled / disabled in project settings' do diff --git a/spec/features/projects/graph_spec.rb b/spec/features/projects/graph_spec.rb index 7b9f79c9f7f..72df84bf905 100644 --- a/spec/features/projects/graph_spec.rb +++ b/spec/features/projects/graph_spec.rb @@ -72,9 +72,9 @@ RSpec.describe 'Project Graph', :js do it 'renders CI graphs' do expect(page).to have_content 'Overall' - expect(page).to have_content 'Pipelines for last week' - expect(page).to have_content 'Pipelines for last month' - expect(page).to have_content 'Pipelines for last year' + expect(page).to have_content 'Last week' + expect(page).to have_content 'Last month' + expect(page).to have_content 'Last year' expect(page).to have_content 'Duration for the last 30 commits' end end diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb index 8f1c31f229f..12c5820a69d 100644 --- a/spec/features/projects/issuable_templates_spec.rb +++ b/spec/features/projects/issuable_templates_spec.rb @@ -95,7 +95,7 @@ RSpec.describe 'issuable templates', :js do let(:bug_template_content) { 'this is merge request bug template' } let(:template_override_warning) { 'Applying a template will replace the existing issue description.' } let(:updated_description) { 'updated merge request description' } - let(:merge_request) { create(:merge_request, :with_diffs, source_project: project) } + let(:merge_request) { create(:merge_request, source_project: project) } before do project.repository.create_file( @@ -154,7 +154,7 @@ RSpec.describe 'issuable templates', :js do let(:template_content) { 'this is a test "feature-proposal" template' } let(:fork_user) { create(:user) } let(:forked_project) { fork_project(project, fork_user, repository: true) } - let(:merge_request) { create(:merge_request, :with_diffs, source_project: forked_project, target_project: project) } + let(:merge_request) { create(:merge_request, source_project: forked_project, target_project: project) } before do sign_out(:user) diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 1557a8a2d72..7811394b541 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -27,40 +27,12 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do describe "GET /:project/jobs" do context 'with no jobs' do before do - stub_experiment(jobs_empty_state: experiment_active) - stub_experiment_for_subject(jobs_empty_state: in_experiment_group) - visit project_jobs_path(project) end - context 'when experiment not active' do - let(:experiment_active) { false } - let(:in_experiment_group) { false } - - it 'shows the empty state control page' do - expect(page).to have_content('No jobs to show') - expect(page).to have_link('Get started with Pipelines') - end - end - - context 'when experiment active and user in control group' do - let(:experiment_active) { true } - let(:in_experiment_group) { false } - - it 'shows the empty state control page' do - expect(page).to have_content('No jobs to show') - expect(page).to have_link('Get started with Pipelines') - end - end - - context 'when experiment active and user in experimental group' do - let(:experiment_active) { true } - let(:in_experiment_group) { true } - - it 'shows the empty state experiment page' do - expect(page).to have_content('Use jobs to automate your tasks') - expect(page).to have_link('Create CI/CD configuration file') - end + it 'shows the empty state page' do + expect(page).to have_content('Use jobs to automate your tasks') + expect(page).to have_link('Create CI/CD configuration file', href: project.present(current_user: user).add_ci_yml_path) end end @@ -102,7 +74,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do it "shows Finished tab jobs" do expect(page).to have_selector('.nav-links li.active', text: 'Finished') - expect(page).to have_content 'No jobs to show' + expect(page).to have_content('Use jobs to automate your tasks') end end @@ -533,10 +505,10 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do expect(page).to have_content('Trigger token') expect(page).to have_content('Trigger variables') - expect(page).not_to have_css('.js-reveal-variables') + expect(page).not_to have_selector('[data-testid="trigger-reveal-values-button"]') - expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1') - expect(page).to have_selector('.js-build-value', text: '••••••') + expect(page).to have_selector('[data-testid="trigger-build-key"]', text: 'TRIGGER_KEY_1') + expect(page).to have_selector('[data-testid="trigger-build-value"]', text: '••••••') end end @@ -571,17 +543,17 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do expect(page).to have_content('Trigger token') expect(page).to have_content('Trigger variables') - expect(page).to have_css('.js-reveal-variables') + expect(page).to have_selector('[data-testid="trigger-reveal-values-button"]') - expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1') - expect(page).to have_selector('.js-build-value', text: '••••••') + expect(page).to have_selector('[data-testid="trigger-build-key"]', text: 'TRIGGER_KEY_1') + expect(page).to have_selector('[data-testid="trigger-build-value"]', text: '••••••') end it 'reveals values on button click', :js do click_button 'Reveal values' - expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1') - expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1') + expect(page).to have_selector('[data-testid="trigger-build-key"]', text: 'TRIGGER_KEY_1') + expect(page).to have_selector('[data-testid="trigger-build-value"]', text: 'TRIGGER_VALUE_1') end end diff --git a/spec/features/projects/members/anonymous_user_sees_members_spec.rb b/spec/features/projects/members/anonymous_user_sees_members_spec.rb index 3b0f00c5494..d710ecf6c88 100644 --- a/spec/features/projects/members/anonymous_user_sees_members_spec.rb +++ b/spec/features/projects/members/anonymous_user_sees_members_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Projects > Members > Anonymous user sees members' do + include Spec::Support::Helpers::Features::MembersHelpers + let(:user) { create(:user) } let(:group) { create(:group, :public) } let(:project) { create(:project, :public) } @@ -12,11 +14,25 @@ RSpec.describe 'Projects > Members > Anonymous user sees members' do create(:project_group_link, project: project, group: group) end - it "anonymous user visits the project's members page and sees the list of members" do - visit project_project_members_path(project) + context 'when `vue_project_members_list` feature flag is enabled', :js do + it "anonymous user visits the project's members page and sees the list of members" do + visit project_project_members_path(project) + + expect(find_member_row(user)).to have_content(user.name) + end + end + + context 'when `vue_project_members_list` feature flag is disabled' do + before do + stub_feature_flags(vue_project_members_list: false) + end + + it "anonymous user visits the project's members page and sees the list of members" do + visit project_project_members_path(project) - expect(current_path).to eq( - project_project_members_path(project)) - expect(page).to have_content(user.name) + expect(current_path).to eq( + project_project_members_path(project)) + expect(page).to have_content(user.name) + end end end diff --git a/spec/features/projects/members/group_members_spec.rb b/spec/features/projects/members/group_members_spec.rb index aa15f04bf24..1abd00421ec 100644 --- a/spec/features/projects/members/group_members_spec.rb +++ b/spec/features/projects/members/group_members_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Projects members', :js do + include Spec::Support::Helpers::Features::MembersHelpers + let(:user) { create(:user) } let(:developer) { create(:user) } let(:group) { create(:group, :public) } @@ -18,117 +20,218 @@ RSpec.describe 'Projects members', :js do sign_in(user) end - context 'with a group invitee' do - before do - group_invitee - visit project_project_members_path(project) - end + context 'when `vue_project_members_list` feature flag is enabled' do + context 'with a group invitee' do + before do + group_invitee + visit project_project_members_path(project) + end - it 'does not appear in the project members page' do - page.within first('.content-list') do - expect(page).not_to have_content('test2@abc.com') + it 'does not appear in the project members page' do + expect(members_table).not_to have_content('test2@abc.com') end end - end - context 'with a group' do - it 'shows group and project members by default' do - visit project_project_members_path(project) + context 'with a group' do + it 'shows group and project members by default' do + visit project_project_members_path(project) + + expect(members_table).to have_content(developer.name) + expect(members_table).to have_content(user.name) + expect(members_table).to have_content(group.name) + end + + it 'shows project members only if requested' do + visit project_project_members_path(project, with_inherited_permissions: 'exclude') + + expect(members_table).to have_content(developer.name) + expect(members_table).not_to have_content(user.name) + expect(members_table).not_to have_content(group.name) + end - page.within first('.content-list') do - expect(page).to have_content(developer.name) + it 'shows group members only if requested' do + visit project_project_members_path(project, with_inherited_permissions: 'only') - expect(page).to have_content(user.name) - expect(page).to have_content(group.name) + expect(members_table).not_to have_content(developer.name) + expect(members_table).to have_content(user.name) + expect(members_table).to have_content(group.name) end end - it 'shows project members only if requested' do - visit project_project_members_path(project, with_inherited_permissions: 'exclude') + context 'with a group, a project invitee, and a project requester' do + before do + group.request_access(group_requester) + project.request_access(project_requester) + group_invitee + project_invitee + visit project_project_members_path(project) + end + + it 'shows the group owner' do + expect(members_table).to have_content(user.name) + expect(members_table).to have_content(group.name) + end + + it 'shows the project developer' do + expect(members_table).to have_content(developer.name) + end + + it 'shows the project invitee' do + click_link 'Invited' + + expect(members_table).to have_content('test1@abc.com') + expect(members_table).not_to have_content('test2@abc.com') + end - page.within first('.content-list') do - expect(page).to have_content(developer.name) + it 'shows the project requester' do + click_link 'Access requests' - expect(page).not_to have_content(user.name) - expect(page).not_to have_content(group.name) + expect(members_table).to have_content(project_requester.name) + expect(members_table).not_to have_content(group_requester.name) end end - it 'shows group members only if requested' do - visit project_project_members_path(project, with_inherited_permissions: 'only') + context 'with a group requester' do + before do + stub_feature_flags(invite_members_group_modal: false) + group.request_access(group_requester) + visit project_project_members_path(project) + end + + it 'does not appear in the project members page' do + expect(page).not_to have_link('Access requests') + expect(members_table).not_to have_content(group_requester.name) + end + end + + context 'showing status of members' do + it 'shows the status' do + create(:user_status, user: user, emoji: 'smirk', message: 'Authoring this object') - page.within first('.content-list') do - expect(page).not_to have_content(developer.name) + visit project_project_members_path(project) - expect(page).to have_content(user.name) - expect(page).to have_content(group.name) + expect(first_row).to have_selector('gl-emoji[data-name="smirk"]') end end end - context 'with a group, a project invitee, and a project requester' do + context 'when `vue_project_members_list` feature flag is disabled' do before do - group.request_access(group_requester) - project.request_access(project_requester) - group_invitee - project_invitee - visit project_project_members_path(project) + stub_feature_flags(vue_project_members_list: false) end - it 'shows the group owner' do - page.within first('.content-list') do - # Group owner - expect(page).to have_content(user.name) - expect(page).to have_content(group.name) + context 'with a group invitee' do + before do + group_invitee + visit project_project_members_path(project) end - end - it 'shows the project developer' do - page.within first('.content-list') do - # Project developer - expect(page).to have_content(developer.name) + it 'does not appear in the project members page' do + page.within first('.content-list') do + expect(page).not_to have_content('test2@abc.com') + end end end - it 'shows the project invitee' do - click_link 'Invited' + context 'with a group' do + it 'shows group and project members by default' do + visit project_project_members_path(project) - page.within first('.content-list') do - expect(page).to have_content('test1@abc.com') - expect(page).not_to have_content('test2@abc.com') + page.within first('.content-list') do + expect(page).to have_content(developer.name) + + expect(page).to have_content(user.name) + expect(page).to have_content(group.name) + end + end + + it 'shows project members only if requested' do + visit project_project_members_path(project, with_inherited_permissions: 'exclude') + + page.within first('.content-list') do + expect(page).to have_content(developer.name) + + expect(page).not_to have_content(user.name) + expect(page).not_to have_content(group.name) + end end - end - it 'shows the project requester' do - click_link 'Access requests' + it 'shows group members only if requested' do + visit project_project_members_path(project, with_inherited_permissions: 'only') - page.within first('.content-list') do - expect(page).to have_content(project_requester.name) - expect(page).not_to have_content(group_requester.name) + page.within first('.content-list') do + expect(page).not_to have_content(developer.name) + + expect(page).to have_content(user.name) + expect(page).to have_content(group.name) + end end end - end - context 'with a group requester' do - before do - stub_feature_flags(invite_members_group_modal: false) - group.request_access(group_requester) - visit project_project_members_path(project) + context 'with a group, a project invitee, and a project requester' do + before do + group.request_access(group_requester) + project.request_access(project_requester) + group_invitee + project_invitee + visit project_project_members_path(project) + end + + it 'shows the group owner' do + page.within first('.content-list') do + # Group owner + expect(page).to have_content(user.name) + expect(page).to have_content(group.name) + end + end + + it 'shows the project developer' do + page.within first('.content-list') do + # Project developer + expect(page).to have_content(developer.name) + end + end + + it 'shows the project invitee' do + click_link 'Invited' + + page.within first('.content-list') do + expect(page).to have_content('test1@abc.com') + expect(page).not_to have_content('test2@abc.com') + end + end + + it 'shows the project requester' do + click_link 'Access requests' + + page.within first('.content-list') do + expect(page).to have_content(project_requester.name) + expect(page).not_to have_content(group_requester.name) + end + end end - it 'does not appear in the project members page' do - expect(page).not_to have_link('Access requests') - page.within first('.content-list') do - expect(page).not_to have_content(group_requester.name) + context 'with a group requester' do + before do + stub_feature_flags(invite_members_group_modal: false) + group.request_access(group_requester) + visit project_project_members_path(project) + end + + it 'does not appear in the project members page' do + expect(page).not_to have_link('Access requests') + page.within first('.content-list') do + expect(page).not_to have_content(group_requester.name) + end end end - end - describe 'showing status of members' do - it_behaves_like 'showing user status' do - let(:user_with_status) { developer } + context 'showing status of members' do + it_behaves_like 'showing user status' do + let(:user_with_status) { developer } - subject { visit project_project_members_path(project) } + subject { visit project_project_members_path(project) } + end 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 686d86b1783..9d087dfd5f6 100644 --- a/spec/features/projects/members/groups_with_access_list_spec.rb +++ b/spec/features/projects/members/groups_with_access_list_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Projects > Members > Groups with access list', :js do + include Spec::Support::Helpers::Features::MembersHelpers + let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group, :public) } let_it_be(:project) { create(:project, :public) } @@ -15,86 +17,172 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do project.add_maintainer(user) sign_in(user) - visit project_project_members_path(project) - click_groups_tab end - it 'updates group access level' do - click_button group_link.human_access - - page.within '.dropdown-menu' do - click_link 'Guest' + context 'when `vue_project_members_list` feature flag is enabled' do + before do + visit project_project_members_path(project) + click_groups_tab end - wait_for_requests + it 'updates group access level' do + click_button group_link.human_access + click_button 'Guest' - visit project_project_members_path(project) + wait_for_requests - click_groups_tab + visit project_project_members_path(project) - expect(first('.group_member')).to have_content('Guest') - end + click_groups_tab + + expect(find_group_row(group)).to have_content('Guest') + end - it 'updates expiry date' do - expires_at_field = "member_expires_at_#{group.id}" - fill_in expires_at_field, with: 3.days.from_now.to_date + it 'updates expiry date' do + page.within find_group_row(group) do + fill_in 'Expiration date', with: 5.days.from_now.to_date + find_field('Expiration date').native.send_keys :enter - find_field(expires_at_field).native.send_keys :enter - wait_for_requests + wait_for_requests - page.within(find('li.group_member')) do - expect(page).to have_content('Expires in 3 days') + expect(page).to have_content(/in \d days/) + end end - end - context 'when link has expiry date set' do - let(:additional_link_attrs) { { expires_at: 3.days.from_now.to_date } } + context 'when link has expiry date set' do + let(:additional_link_attrs) { { expires_at: 5.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') + it 'clears expiry date' do + page.within find_group_row(group) do + expect(page).to have_content(/in \d days/) - page.within(find('.js-edit-member-form')) do - find('.js-clear-input').click + find('[data-testid="clear-button"]').click + + wait_for_requests + + expect(page).to have_content('No expiration set') end + end + end - wait_for_requests + it 'deletes group link' do + expect(page).to have_content(group.full_name) + + page.within find_group_row(group) do + click_button 'Remove group' + end - expect(page).not_to have_content('Expires in') + page.within('[role="dialog"]') do + click_button('Remove group') + end + + expect(page).not_to have_content(group.full_name) + end + + context 'search in existing members' do + it 'finds no results' do + fill_in_filtered_search 'Search groups', with: 'testing 123' + + click_groups_tab + + expect(page).not_to have_content(group.full_name) + end + + it 'finds results' do + fill_in_filtered_search 'Search groups', with: group.full_name + + click_groups_tab + + expect(members_table).to have_content(group.full_name) end end end - it 'deletes group link' do - page.within(first('.group_member')) do - accept_confirm { find('.btn-danger').click } + context 'when `vue_project_members_list` feature flag is disabled' do + before do + stub_feature_flags(vue_project_members_list: false) + + visit project_project_members_path(project) + click_groups_tab end - wait_for_requests - expect(page).not_to have_selector('.group_member') - end + it 'updates group access level' do + click_button group_link.human_access - context 'search in existing members' do - it 'finds no results' do - page.within '.user-search-form' do - fill_in 'search_groups', with: 'testing 123' - find('.user-search-btn').click + page.within '.dropdown-menu' do + click_link 'Guest' end + wait_for_requests + + visit project_project_members_path(project) + click_groups_tab + expect(first('.group_member')).to have_content('Guest') + end + + it 'updates expiry date' do + expires_at_field = "member_expires_at_#{group.id}" + fill_in expires_at_field, with: 3.days.from_now.to_date + + 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 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-danger').click } + end + wait_for_requests + expect(page).not_to have_selector('.group_member') end - it 'finds results' do - page.within '.user-search-form' do - fill_in 'search_groups', with: group.name - find('.user-search-btn').click + context 'search in existing members' do + it 'finds no results' do + page.within '.user-search-form' do + fill_in 'search_groups', with: 'testing 123' + find('.user-search-btn').click + end + + click_groups_tab + + expect(page).not_to have_selector('.group_member') end - click_groups_tab + it 'finds results' do + page.within '.user-search-form' do + fill_in 'search_groups', with: group.name + find('.user-search-btn').click + end + + click_groups_tab - expect(page).to have_selector('.group_member', count: 1) + expect(page).to have_selector('.group_member', count: 1) + end end end diff --git a/spec/features/projects/members/invite_group_spec.rb b/spec/features/projects/members/invite_group_spec.rb index bb56ae348fb..f0d115fef1d 100644 --- a/spec/features/projects/members/invite_group_spec.rb +++ b/spec/features/projects/members/invite_group_spec.rb @@ -5,9 +5,14 @@ require 'spec_helper' RSpec.describe 'Project > Members > Invite group', :js do include Select2Helper include ActionView::Helpers::DateHelper + include Spec::Support::Helpers::Features::MembersHelpers let(:maintainer) { create(:user) } + before do + stub_feature_flags(invite_members_group_modal: false) + end + describe 'Share with group lock' do shared_examples 'the project can be shared with groups' do it 'the "Invite group" tab exists' do @@ -36,21 +41,45 @@ RSpec.describe 'Project > Members > Invite group', :js do context 'when the group has "Share with group lock" disabled' do it_behaves_like 'the project can be shared with groups' - it 'the project can be shared with another group' do - visit project_project_members_path(project) + context 'when `vue_project_members_list` feature flag is enabled' do + it 'the project can be shared with another group' do + visit project_project_members_path(project) - expect(page).not_to have_link 'Groups' + expect(page).not_to have_link 'Groups' - click_on 'invite-group-tab' + click_on 'invite-group-tab' - select2 group_to_share_with.id, from: '#link_group_id' - page.find('body').click - find('.btn-success').click + select2 group_to_share_with.id, from: '#link_group_id' + page.find('body').click + find('.btn-success').click - click_link 'Groups' + click_link 'Groups' - page.within('[data-testid="project-member-groups"]') do - expect(page).to have_content(group_to_share_with.name) + expect(members_table).to have_content(group_to_share_with.name) + end + end + + context 'when `vue_project_members_list` feature flag is disabled' do + before do + stub_feature_flags(vue_project_members_list: false) + end + + it 'the project can be shared with another group' do + visit project_project_members_path(project) + + expect(page).not_to have_link 'Groups' + + click_on 'invite-group-tab' + + select2 group_to_share_with.id, from: '#link_group_id' + page.find('body').click + find('.btn-success').click + + click_link 'Groups' + + page.within('[data-testid="project-member-groups"]') do + expect(page).to have_content(group_to_share_with.name) + end end end end @@ -117,7 +146,7 @@ RSpec.describe 'Project > Members > Invite group', :js do freeze_time { example.run } end - before do + def setup project.add_maintainer(maintainer) group.add_guest(maintainer) sign_in(maintainer) @@ -128,20 +157,37 @@ RSpec.describe 'Project > Members > Invite group', :js do select2 group.id, from: '#link_group_id' - fill_in 'expires_at_groups', with: (Time.now + 4.5.days).strftime('%Y-%m-%d') + fill_in 'expires_at_groups', with: 5.days.from_now.strftime('%Y-%m-%d') click_on 'invite-group-tab' find('.btn-success').click end - it 'the group link shows the expiration time with a warning class' do - click_link 'Groups' + context 'when `vue_project_members_list` feature flag is enabled' do + it 'the group link shows the expiration time with a warning class' do + setup + click_link 'Groups' + + expect(find_group_row(group)).to have_content(/in \d days/) + expect(find_group_row(group)).to have_selector('.gl-text-orange-500') + end + end + + context 'when `vue_project_members_list` feature flag is disabled' do + before do + stub_feature_flags(vue_project_members_list: false) + end - page.within('[data-testid="project-member-groups"]') do - # Using distance_of_time_in_words_to_now because it is not the same as - # subtraction, and this way avoids time zone issues as well - expires_in_text = distance_of_time_in_words_to_now(project.project_group_links.first.expires_at) - expect(page).to have_content(expires_in_text) - expect(page).to have_selector('.text-warning') + it 'the group link shows the expiration time with a warning class' do + setup + click_link 'Groups' + + page.within('[data-testid="project-member-groups"]') do + # Using distance_of_time_in_words_to_now because it is not the same as + # subtraction, and this way avoids time zone issues as well + expires_in_text = distance_of_time_in_words_to_now(project.project_group_links.first.expires_at) + expect(page).to have_content(expires_in_text) + expect(page).to have_selector('.text-warning') + end end end end diff --git a/spec/features/projects/members/list_spec.rb b/spec/features/projects/members/list_spec.rb index 62115f2dce6..b0fe5b9c48a 100644 --- a/spec/features/projects/members/list_spec.rb +++ b/spec/features/projects/members/list_spec.rb @@ -4,7 +4,6 @@ require 'spec_helper' RSpec.describe 'Project members list' do include Select2Helper - include Spec::Support::Helpers::Features::ListRowsHelpers let(:user1) { create(:user, name: 'John Doe') } let(:user2) { create(:user, name: 'Mary Jane') } @@ -13,102 +12,215 @@ RSpec.describe 'Project members list' do before do stub_feature_flags(invite_members_group_modal: false) + sign_in(user1) group.add_owner(user1) end - it 'show members from project and group' do - project.add_developer(user2) + context 'when `vue_project_members_list` feature flag is enabled', :js do + include Spec::Support::Helpers::Features::MembersHelpers - visit_members_page + it 'pushes `vue_project_members_list` feature flag to the frontend' do + visit_members_page - expect(first_row.text).to include(user1.name) - expect(second_row.text).to include(user2.name) - end + expect(page).to have_pushed_frontend_feature_flags(vueProjectMembersList: true) + end - it 'show user once if member of both group and project' do - project.add_developer(user1) + it 'show members from project and group' do + project.add_developer(user2) - visit_members_page + visit_members_page - expect(first_row.text).to include(user1.name) - expect(second_row).to be_blank - end + expect(first_row).to have_content(user1.name) + expect(second_row).to have_content(user2.name) + end + + it 'show user once if member of both group and project' do + project.add_developer(user1) - it 'update user access level', :js do - project.add_developer(user2) + visit_members_page + + expect(first_row).to have_content(user1.name) + expect(second_row).to be_blank + end + + it 'update user access level', :js do + project.add_developer(user2) - visit_members_page + visit_members_page - page.within(second_row) do - click_button('Developer') - click_link('Reporter') + page.within find_member_row(user2) do + click_button('Developer') + click_button('Reporter') - expect(page).to have_button('Reporter') + expect(page).to have_button('Reporter') + end end - end - it 'add user to project', :js do - visit_members_page + it 'add user to project', :js do + visit_members_page - add_user(user2.id, 'Reporter') + add_user(user2.id, 'Reporter') - page.within(second_row) do - expect(page).to have_content(user2.name) - expect(page).to have_button('Reporter') + page.within find_member_row(user2) do + expect(page).to have_button('Reporter') + end end - end - it 'remove user from project', :js do - other_user = create(:user) - project.add_developer(other_user) + it 'remove user from project', :js do + other_user = create(:user) + project.add_developer(other_user) - visit_members_page + visit_members_page - # Open modal - find(:css, 'li.project_member', text: other_user.name).find(:css, 'button.btn-danger').click + # Open modal + page.within find_member_row(other_user) do + click_button 'Remove member' + end - expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests' + page.within('[role="dialog"]') do + expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests' + click_button('Remove member') + end - click_on('Remove member') + wait_for_requests - wait_for_requests + expect(members_table).not_to have_content(other_user.name) + end - expect(page).not_to have_content(other_user.name) - expect(project.users).not_to include(other_user) - end + it 'invite user to project', :js do + visit_members_page + + add_user('test@example.com', 'Reporter') + + click_link 'Invited' + + page.within find_invited_member_row('test@example.com') do + expect(page).to have_button('Reporter') + end + end - it 'invite user to project', :js do - visit_members_page + context 'project bots' do + let(:project_bot) { create(:user, :project_bot, name: 'project_bot') } - add_user('test@example.com', 'Reporter') + before do + project.add_maintainer(project_bot) + end - click_link 'Invited' + it 'does not show form used to change roles and "Expiration date" or the remove user button' do + visit_members_page - page.within(first_row) do - expect(page).to have_content('test@example.com') - expect(page).to have_content('Invited') - expect(page).to have_button('Reporter') + page.within find_member_row(project_bot) do + expect(page).not_to have_button('Maintainer') + expect(page).to have_field('Expiration date', disabled: true) + expect(page).not_to have_button('Remove member') + end + end end end - context 'project bots' do - let(:project_bot) { create(:user, :project_bot, name: 'project_bot') } + context 'when `vue_project_members_list` feature flag is disabled' do + include Spec::Support::Helpers::Features::ListRowsHelpers before do - project.add_maintainer(project_bot) + stub_feature_flags(vue_project_members_list: false) + end + + it 'show members from project and group' do + project.add_developer(user2) + + visit_members_page + + expect(first_row.text).to include(user1.name) + expect(second_row.text).to include(user2.name) + end + + it 'show user once if member of both group and project' do + project.add_developer(user1) + + visit_members_page + + expect(first_row.text).to include(user1.name) + expect(second_row).to be_blank end - it 'does not show form used to change roles and "Expiration date" or the remove user button' do - project_member = project.project_members.find_by(user_id: project_bot.id) + it 'update user access level', :js do + project.add_developer(user2) + + visit_members_page + + page.within(second_row) do + click_button('Developer') + click_link('Reporter') + + expect(page).to have_button('Reporter') + end + end + it 'add user to project', :js do visit_members_page - expect(page).not_to have_selector("#edit_project_member_#{project_member.id}") - expect(page).to have_no_selector("#project_member_#{project_member.id} .btn-danger") + add_user(user2.id, 'Reporter') + + page.within(second_row) do + expect(page).to have_content(user2.name) + expect(page).to have_button('Reporter') + end + end + + it 'remove user from project', :js do + other_user = create(:user) + project.add_developer(other_user) + + visit_members_page + + # Open modal + find(:css, 'li.project_member', text: other_user.name).find(:css, 'button.btn-danger').click + + expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests' + + click_on('Remove member') + + wait_for_requests + + expect(page).not_to have_content(other_user.name) + expect(project.users).not_to include(other_user) + end + + it 'invite user to project', :js do + visit_members_page + + add_user('test@example.com', 'Reporter') + + click_link 'Invited' + + page.within(first_row) do + expect(page).to have_content('test@example.com') + expect(page).to have_content('Invited') + expect(page).to have_button('Reporter') + end + end + + context 'project bots' do + let(:project_bot) { create(:user, :project_bot, name: 'project_bot') } + + before do + project.add_maintainer(project_bot) + end + + it 'does not show form used to change roles and "Expiration date" or the remove user button' do + project_member = project.project_members.find_by(user_id: project_bot.id) + + visit_members_page + + expect(page).not_to have_selector("#edit_project_member_#{project_member.id}") + expect(page).to have_no_selector("#project_member_#{project_member.id} .btn-danger") + end end end + private + def add_user(id, role) page.within ".invite-users-form" do select2(id, from: "#user_ids", multiple: true) 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 d69c3f2652c..1127c64e0c7 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 @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe 'Projects > Members > Maintainer adds member with expiration date', :js do include Select2Helper include ActiveSupport::Testing::TimeHelpers + include Spec::Support::Helpers::Features::MembersHelpers let_it_be(:maintainer) { create(:user) } let_it_be(:project) { create(:project) } @@ -17,49 +18,107 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date sign_in(maintainer) end - it 'expiration date is displayed in the members list' do - visit project_project_members_path(project) + context 'when `vue_project_members_list` feature flag is enabled' do + it 'expiration date is displayed in the members list' do + stub_feature_flags(invite_members_group_modal: false) - page.within '.invite-users-form' do - select2(new_member.id, from: '#user_ids', multiple: true) + visit project_project_members_path(project) - fill_in 'expires_at', with: 3.days.from_now.to_date - find_field('expires_at').native.send_keys :enter + page.within '.invite-users-form' do + select2(new_member.id, from: '#user_ids', multiple: true) - click_on 'Invite' + fill_in 'expires_at', with: 5.days.from_now.to_date + find_field('expires_at').native.send_keys :enter + + click_on 'Invite' + end + + page.within find_member_row(new_member) do + expect(page).to have_content(/in \d days/) + end end - page.within "#project_member_#{project_member_id}" do - expect(page).to have_content('Expires in 3 days') + it 'changes 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 find_member_row(new_member) do + fill_in 'Expiration date', with: 5.days.from_now.to_date + find_field('Expiration date').native.send_keys :enter + + wait_for_requests + + expect(page).to have_content(/in \d days/) + end 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) + it 'clears expiration date' do + project.team.add_users([new_member.id], :developer, expires_at: 5.days.from_now.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 + page.within find_member_row(new_member) do + expect(page).to have_content(/in \d days/) - wait_for_requests + find('[data-testid="clear-button"]').click - expect(page).to have_content('Expires in 3 days') + wait_for_requests + + expect(page).to have_content('No expiration set') + end end 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) + context 'when `vue_project_members_list` feature flag is disabled' do + before do + stub_feature_flags(vue_project_members_list: false) + end + + it 'expiration date is displayed in the members list' do + stub_feature_flags(invite_members_group_modal: false) + + visit project_project_members_path(project) + + page.within '.invite-users-form' do + select2(new_member.id, from: '#user_ids', multiple: true) + + fill_in 'expires_at', with: 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: 1.day.from_now.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 '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') + page.within "#project_member_#{project_member_id}" do + expect(page).to have_content('Expires in 3 days') - find('.js-clear-input').click + find('.js-clear-input').click - wait_for_requests + wait_for_requests - expect(page).not_to have_content('Expires in') + expect(page).not_to have_content('Expires in') + end end end diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb index be27cbc0d66..3c132747bc4 100644 --- a/spec/features/projects/members/sorting_spec.rb +++ b/spec/features/projects/members/sorting_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Projects > Members > Sorting' do + include Spec::Support::Helpers::Features::MembersHelpers + let(:maintainer) { create(:user, name: 'John Doe') } let(:developer) { create(:user, name: 'Mary Jane', last_sign_in_at: 5.days.ago) } let(:project) { create(:project, namespace: maintainer.namespace, creator: maintainer) } @@ -13,78 +15,169 @@ RSpec.describe 'Projects > Members > Sorting' do sign_in(maintainer) end - it 'sorts alphabetically by default' do - visit_members_list(sort: nil) + context 'when `vue_project_members_list` feature flag is enabled', :js do + it 'sorts by account by default' do + visit_members_list(sort: nil) - expect(first_member).to include(maintainer.name) - expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') - end + expect(first_row).to have_content(maintainer.name) + expect(second_row).to have_content(developer.name) - it 'sorts by access level ascending' do - visit_members_list(sort: :access_level_asc) + expect_sort_by('Account', :asc) + end - expect(first_member).to include(developer.name) - expect(second_member).to include(maintainer.name) - expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending') - end + it 'sorts by max role ascending' do + visit_members_list(sort: :access_level_asc) - it 'sorts by access level descending' do - visit_members_list(sort: :access_level_desc) + expect(first_row).to have_content(developer.name) + expect(second_row).to have_content(maintainer.name) - expect(first_member).to include(maintainer.name) - expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending') - end + expect_sort_by('Max role', :asc) + end - it 'sorts by last joined' do - visit_members_list(sort: :last_joined) + it 'sorts by max role descending' do + visit_members_list(sort: :access_level_desc) - expect(first_member).to include(maintainer.name) - expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Last joined') - end + expect(first_row).to have_content(maintainer.name) + expect(second_row).to have_content(developer.name) - it 'sorts by oldest joined' do - visit_members_list(sort: :oldest_joined) + expect_sort_by('Max role', :desc) + end - expect(first_member).to include(developer.name) - expect(second_member).to include(maintainer.name) - expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined') - end + it 'sorts by access granted ascending' do + visit_members_list(sort: :last_joined) - it 'sorts by name ascending' do - visit_members_list(sort: :name_asc) + expect(first_row).to have_content(maintainer.name) + expect(second_row).to have_content(developer.name) - expect(first_member).to include(maintainer.name) - expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') - end + expect_sort_by('Access granted', :asc) + end - it 'sorts by name descending' do - visit_members_list(sort: :name_desc) + it 'sorts by access granted descending' do + visit_members_list(sort: :oldest_joined) - expect(first_member).to include(developer.name) - expect(second_member).to include(maintainer.name) - expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') - end + expect(first_row).to have_content(developer.name) + expect(second_row).to have_content(maintainer.name) + + expect_sort_by('Access granted', :desc) + end + + it 'sorts by account ascending' do + visit_members_list(sort: :name_asc) - it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do - visit_members_list(sort: :recent_sign_in) + expect(first_row).to have_content(maintainer.name) + expect(second_row).to have_content(developer.name) - expect(first_member).to include(maintainer.name) - expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') + expect_sort_by('Account', :asc) + end + + it 'sorts by account descending' do + visit_members_list(sort: :name_desc) + + expect(first_row).to have_content(developer.name) + expect(second_row).to have_content(maintainer.name) + + expect_sort_by('Account', :desc) + end + + it 'sorts by last sign-in ascending', :clean_gitlab_redis_shared_state do + visit_members_list(sort: :recent_sign_in) + + expect(first_row).to have_content(maintainer.name) + expect(second_row).to have_content(developer.name) + + expect_sort_by('Last sign-in', :asc) + end + + it 'sorts by last sign-in descending', :clean_gitlab_redis_shared_state do + visit_members_list(sort: :oldest_sign_in) + + expect(first_row).to have_content(developer.name) + expect(second_row).to have_content(maintainer.name) + + expect_sort_by('Last sign-in', :desc) + end end - it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do - visit_members_list(sort: :oldest_sign_in) + context 'when `vue_project_members_list` feature flag is disabled' do + before do + stub_feature_flags(vue_project_members_list: false) + end + + it 'sorts alphabetically by default' do + visit_members_list(sort: nil) - expect(first_member).to include(developer.name) - expect(second_member).to include(maintainer.name) - expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in') + expect(first_member).to include(maintainer.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') + end + + it 'sorts by access level ascending' do + visit_members_list(sort: :access_level_asc) + + expect(first_member).to include(developer.name) + expect(second_member).to include(maintainer.name) + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending') + end + + it 'sorts by access level descending' do + visit_members_list(sort: :access_level_desc) + + expect(first_member).to include(maintainer.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending') + end + + it 'sorts by last joined' do + visit_members_list(sort: :last_joined) + + expect(first_member).to include(maintainer.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Last joined') + end + + it 'sorts by oldest joined' do + visit_members_list(sort: :oldest_joined) + + expect(first_member).to include(developer.name) + expect(second_member).to include(maintainer.name) + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined') + end + + it 'sorts by name ascending' do + visit_members_list(sort: :name_asc) + + expect(first_member).to include(maintainer.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') + end + + it 'sorts by name descending' do + visit_members_list(sort: :name_desc) + + expect(first_member).to include(developer.name) + expect(second_member).to include(maintainer.name) + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') + end + + it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do + visit_members_list(sort: :recent_sign_in) + + expect(first_member).to include(maintainer.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') + end + + it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do + visit_members_list(sort: :oldest_sign_in) + + expect(first_member).to include(developer.name) + expect(second_member).to include(maintainer.name) + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in') + end end + private + def visit_members_list(sort:) visit project_project_members_path(project, sort: sort) end @@ -96,4 +189,11 @@ RSpec.describe 'Projects > Members > Sorting' do def second_member page.all('ul.content-list > li').last.text end + + def expect_sort_by(text, sort_direction) + within('[data-testid="members-sort-dropdown"]') do + expect(page).to have_css('button[aria-haspopup="true"]', text: text) + expect(page).to have_button("Sorting Direction: #{sort_direction == :asc ? 'Ascending' : 'Descending'}") + end + end end diff --git a/spec/features/projects/members/tabs_spec.rb b/spec/features/projects/members/tabs_spec.rb index bdcf02c82a4..eef3395de91 100644 --- a/spec/features/projects/members/tabs_spec.rb +++ b/spec/features/projects/members/tabs_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' RSpec.describe 'Projects > Members > Tabs' do + include Spec::Support::Helpers::Features::MembersHelpers using RSpec::Parameterized::TableSyntax let_it_be(:user) { create(:user) } @@ -19,55 +20,93 @@ RSpec.describe 'Projects > Members > Tabs' do end end - before do - allow(Kaminari.config).to receive(:default_per_page).and_return(1) - - sign_in(user) - visit project_project_members_path(project) - end + context 'tabs' do + before do + sign_in(user) + visit project_project_members_path(project) + end - where(:tab, :count) do - 'Members' | 3 - 'Invited' | 2 - 'Groups' | 2 - 'Access requests' | 2 - end + where(:tab, :count) do + 'Members' | 3 + 'Invited' | 2 + 'Groups' | 2 + 'Access requests' | 2 + end - with_them do - it "renders #{params[:tab]} tab" do - expect(page).to have_selector('.nav-link', text: "#{tab} #{count}") + with_them do + it "renders #{params[:tab]} tab" do + expect(page).to have_selector('.nav-link', text: "#{tab} #{count}") + end end - end - context 'displays "Members" tab by default' do - it_behaves_like 'active "Members" tab' + context 'displays "Members" tab by default' do + it_behaves_like 'active "Members" tab' + end end - context 'when searching "Groups"', :js do + context 'when `vue_project_members_list` feature flag is enabled' do before do - click_link 'Groups' + sign_in(user) + visit project_project_members_path(project) + end + + context 'when searching "Groups"', :js do + before do + click_link 'Groups' + + fill_in_filtered_search 'Search groups', with: 'group' + end - page.within '[data-testid="group-link-search-form"]' do - fill_in 'search_groups', with: 'group' - find('button[type="submit"]').click + it 'displays "Groups" tab' do + expect(page).to have_selector('.nav-link.active', text: 'Groups') + end + + context 'and then searching "Members"' do + before do + click_link 'Members 3' + + fill_in_filtered_search 'Filter members', with: 'user' + end + + it_behaves_like 'active "Members" tab' end end + end + + context 'when `vue_project_members_list` feature flag is disabled' do + before do + stub_feature_flags(vue_project_members_list: false) - it 'displays "Groups" tab' do - expect(page).to have_selector('.nav-link.active', text: 'Groups') + sign_in(user) + visit project_project_members_path(project) end - context 'and then searching "Members"' do + context 'when searching "Groups"', :js do before do - click_link 'Members 3' + click_link 'Groups' - page.within '[data-testid="user-search-form"]' do - fill_in 'search', with: 'user' + page.within '[data-testid="group-link-search-form"]' do + fill_in 'search_groups', with: 'group' find('button[type="submit"]').click end end - it_behaves_like 'active "Members" tab' + it 'displays "Groups" tab' do + expect(page).to have_selector('.nav-link.active', text: 'Groups') + end + + context 'and then searching "Members"' do + before do + click_link 'Members 3' + + page.within '[data-testid="user-search-form"]' do + fill_in 'search', with: 'user' + find('button[type="submit"]').click + end + end + + it_behaves_like 'active "Members" tab' + end end end end diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb index 25791b393bc..4ff3827b240 100644 --- a/spec/features/projects/navbar_spec.rb +++ b/spec/features/projects/navbar_spec.rb @@ -67,23 +67,4 @@ RSpec.describe 'Project navbar' do it_behaves_like 'verified navigation bar' end - - context 'when invite team members is not available' do - it 'does not display the js-invite-members-trigger' do - visit project_path(project) - - expect(page).not_to have_selector('.js-invite-members-trigger') - end - end - - context 'when invite team members is available' do - it 'includes the div for js-invite-members-trigger' do - stub_feature_flags(invite_members_group_modal: true) - allow_any_instance_of(InviteMembersHelper).to receive(:invite_members_allowed?).and_return(true) - - visit project_path(project) - - expect(page).to have_selector('.js-invite-members-trigger') - end - end end diff --git a/spec/features/projects/pages/user_adds_domain_spec.rb b/spec/features/projects/pages/user_adds_domain_spec.rb new file mode 100644 index 00000000000..24c9edb79e5 --- /dev/null +++ b/spec/features/projects/pages/user_adds_domain_spec.rb @@ -0,0 +1,185 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe 'User adds pages domain', :js do + include LetsEncryptHelpers + + let_it_be(:project) { create(:project, pages_https_only: false) } + let(:user) { create(:user) } + + before do + allow(Gitlab.config.pages).to receive(:enabled).and_return(true) + + project.add_maintainer(user) + + sign_in(user) + end + + context 'when pages are exposed on external HTTP address', :http_pages_enabled do + let(:project) { create(:project, pages_https_only: false) } + + shared_examples 'adds new domain' do + it 'adds new domain' do + visit new_project_pages_domain_path(project) + + fill_in 'Domain', with: 'my.test.domain.com' + click_button 'Create New Domain' + + expect(page).to have_content('my.test.domain.com') + end + end + + it 'allows to add new domain' do + visit project_pages_path(project) + + expect(page).to have_content('New Domain') + end + + it_behaves_like 'adds new domain' + + context 'when project in group namespace' do + it_behaves_like 'adds new domain' do + let(:group) { create :group } + let(:project) { create(:project, namespace: group, pages_https_only: false) } + end + end + + context 'when pages domain is added' do + before do + create(:pages_domain, project: project, domain: 'my.test.domain.com') + + visit new_project_pages_domain_path(project) + end + + it 'renders certificates is disabled' do + expect(page).to have_content('Support for custom certificates is disabled') + end + + it 'does not adds new domain and renders error message' do + fill_in 'Domain', with: 'my.test.domain.com' + click_button 'Create New Domain' + + expect(page).to have_content('Domain has already been taken') + end + end + end + + context 'when pages are exposed on external HTTPS address', :https_pages_enabled, :js do + let(:certificate_pem) do + attributes_for(:pages_domain)[:certificate] + end + + let(:certificate_key) do + attributes_for(:pages_domain)[:key] + end + + it 'adds new domain with certificate' do + visit new_project_pages_domain_path(project) + + fill_in 'Domain', with: 'my.test.domain.com' + + fill_in 'Certificate (PEM)', with: certificate_pem + fill_in 'Key (PEM)', with: certificate_key + click_button 'Create New Domain' + + expect(page).to have_content('my.test.domain.com') + end + + it "adds new domain with certificate if Let's Encrypt is enabled" do + stub_lets_encrypt_settings + + visit new_project_pages_domain_path(project) + + fill_in 'Domain', with: 'my.test.domain.com' + + find('.js-auto-ssl-toggle-container .project-feature-toggle').click + + fill_in 'Certificate (PEM)', with: certificate_pem + fill_in 'Key (PEM)', with: certificate_key + click_button 'Create New Domain' + + expect(page).to have_content('my.test.domain.com') + end + + it 'shows validation error if domain is duplicated' do + project.pages_domains.create!(domain: 'my.test.domain.com') + + visit new_project_pages_domain_path(project) + + fill_in 'Domain', with: 'my.test.domain.com' + click_button 'Create New Domain' + + expect(page).to have_content('Domain has already been taken') + end + + describe 'with dns verification enabled' do + before do + stub_application_setting(pages_domain_verification_enabled: true) + end + + it 'shows the DNS verification record' do + domain = create(:pages_domain, project: project) + + visit project_pages_path(project) + + within('#content-body') { click_link 'Edit' } + expect(page).to have_field :domain_verification, with: "#{domain.verification_domain} TXT #{domain.keyed_verification_code}" + end + end + + describe 'updating the certificate for an existing domain' do + let!(:domain) do + create(:pages_domain, project: project, auto_ssl_enabled: false) + end + + it 'allows the certificate to be updated' do + visit project_pages_path(project) + + within('#content-body') { click_link 'Edit' } + click_button 'Save Changes' + + expect(page).to have_content('Domain was updated') + end + + context 'when the certificate is invalid' do + let!(:domain) do + create(:pages_domain, :without_certificate, :without_key, project: project) + end + + it 'tells the user what the problem is' do + visit project_pages_path(project) + + within('#content-body') { click_link 'Edit' } + + fill_in 'Certificate (PEM)', with: 'invalid data' + click_button 'Save Changes' + + expect(page).to have_content('Certificate must be a valid PEM certificate') + expect(page).to have_content('Certificate misses intermediates') + expect(page).to have_content("Key doesn't match the certificate") + end + end + + it 'allows the certificate to be removed', :js do + visit project_pages_path(project) + + within('#content-body') { click_link 'Edit' } + + accept_confirm { click_link 'Remove' } + + expect(page).to have_field('Certificate (PEM)', with: '') + expect(page).to have_field('Key (PEM)', with: '') + domain.reload + expect(domain.certificate).to be_nil + expect(domain.key).to be_nil + end + + it 'shows the DNS CNAME record' do + visit project_pages_path(project) + + within('#content-body') { click_link 'Edit' } + expect(page).to have_field :domain_dns, with: "#{domain.domain} CNAME #{domain.project.pages_subdomain}.#{Settings.pages.host}." + end + end + end +end diff --git a/spec/features/projects/pages_lets_encrypt_spec.rb b/spec/features/projects/pages/user_edits_lets_encrypt_settings_spec.rb index 302e9f5e533..cf8438d5e6f 100644 --- a/spec/features/projects/pages_lets_encrypt_spec.rb +++ b/spec/features/projects/pages/user_edits_lets_encrypt_settings_spec.rb @@ -17,7 +17,7 @@ RSpec.describe "Pages with Let's Encrypt", :https_pages_enabled do project.add_role(user, role) sign_in(user) - project.namespace.update(owner: user) + project.namespace.update!(owner: user) allow_next_instance_of(Project) do |instance| allow(instance).to receive(:pages_deployed?) { true } end diff --git a/spec/features/projects/pages/user_edits_settings_spec.rb b/spec/features/projects/pages/user_edits_settings_spec.rb new file mode 100644 index 00000000000..3649fae17ce --- /dev/null +++ b/spec/features/projects/pages/user_edits_settings_spec.rb @@ -0,0 +1,201 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe 'Pages edits pages settings', :js do + let(:project) { create(:project, pages_https_only: false) } + let(:user) { create(:user) } + + before do + allow(Gitlab.config.pages).to receive(:enabled).and_return(true) + + project.add_maintainer(user) + + sign_in(user) + end + + context 'when user is the owner' do + before do + project.namespace.update!(owner: user) + end + + context 'when pages deployed' do + before do + project.mark_pages_as_deployed + end + + it 'renders Access pages' do + visit project_pages_path(project) + + expect(page).to have_content('Access pages') + end + + context 'when pages are disabled in the project settings' do + it 'renders disabled warning' do + project.project_feature.update!(pages_access_level: ProjectFeature::DISABLED) + + visit project_pages_path(project) + + expect(page).to have_content('GitLab Pages are disabled for this project') + end + end + + it 'renders first deployment warning' do + visit project_pages_path(project) + + expect(page).to have_content('It may take up to 30 minutes before the site is available after the first deployment.') + end + + shared_examples 'does not render access control warning' do + it 'does not render access control warning' do + visit project_pages_path(project) + + expect(page).not_to have_content('Access Control is enabled for this Pages website') + end + end + + include_examples 'does not render access control warning' + + context 'when access control is enabled in gitlab settings' do + before do + stub_pages_setting(access_control: true) + end + + it 'renders access control warning' do + visit project_pages_path(project) + + expect(page).to have_content('Access Control is enabled for this Pages website') + end + + context 'when pages are public' do + before do + project.project_feature.update!(pages_access_level: ProjectFeature::PUBLIC) + end + + include_examples 'does not render access control warning' + end + end + + context 'when support for external domains is disabled' do + it 'renders message that support is disabled' do + visit project_pages_path(project) + + expect(page).to have_content('Support for domains and certificates is disabled') + end + end + end + + it 'does not see anything to destroy' do + visit project_pages_path(project) + + expect(page).to have_content('Configure pages') + expect(page).not_to have_link('Remove pages') + end + + describe 'project settings page' do + it 'renders "Pages" tab' do + visit edit_project_path(project) + + page.within '.nav-sidebar' do + expect(page).to have_link('Pages') + end + end + + context 'when pages are disabled' do + before do + allow(Gitlab.config.pages).to receive(:enabled).and_return(false) + end + + it 'does not render "Pages" tab' do + visit edit_project_path(project) + + page.within '.nav-sidebar' do + expect(page).not_to have_link('Pages') + end + end + end + end + end + + describe 'HTTPS settings', :https_pages_enabled do + before do + project.namespace.update!(owner: user) + + project.mark_pages_as_deployed + end + + it 'tries to change the setting' do + visit project_pages_path(project) + expect(page).to have_content("Force HTTPS (requires valid certificates)") + + uncheck :project_pages_https_only + + click_button 'Save' + + expect(page).to have_text('Your changes have been saved') + expect(page).not_to have_checked_field('project_pages_https_only') + end + + context 'setting could not be updated' do + let(:service) { instance_double('Projects::UpdateService') } + + before do + allow(Projects::UpdateService).to receive(:new).and_return(service) + allow(service).to receive(:execute).and_return(status: :error, message: 'Some error has occured') + end + + it 'tries to change the setting' do + visit project_pages_path(project) + + uncheck :project_pages_https_only + + click_button 'Save' + + expect(page).to have_text('Some error has occured') + end + end + + context 'non-HTTPS domain exists' do + let(:project) { create(:project, pages_https_only: false) } + + before do + create(:pages_domain, :without_key, :without_certificate, project: project) + end + + it 'the setting is disabled' do + visit project_pages_path(project) + + expect(page).to have_field(:project_pages_https_only, disabled: true) + expect(page).to have_button('Save') + end + end + + context 'HTTPS pages are disabled', :https_pages_disabled do + it 'the setting is unavailable' do + visit project_pages_path(project) + + expect(page).not_to have_field(:project_pages_https_only) + expect(page).not_to have_content('Force HTTPS (requires valid certificates)') + expect(page).to have_button('Save') + end + end + end + + describe 'Remove page' do + context 'when pages are deployed' do + before do + project.mark_pages_as_deployed + end + + it 'removes the pages', :sidekiq_inline do + visit project_pages_path(project) + + expect(page).to have_link('Remove pages') + + accept_confirm { click_link 'Remove pages' } + + expect(page).to have_content('Pages were scheduled for removal') + expect(project.reload.pages_deployed?).to be_falsey + end + end + end +end diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb deleted file mode 100644 index 11f712fde81..00000000000 --- a/spec/features/projects/pages_spec.rb +++ /dev/null @@ -1,411 +0,0 @@ -# frozen_string_literal: true -require 'spec_helper' - -RSpec.shared_examples 'pages settings editing' do - let_it_be(:project) { create(:project, pages_https_only: false) } - let(:user) { create(:user) } - let(:role) { :maintainer } - - before do - allow(Gitlab.config.pages).to receive(:enabled).and_return(true) - - project.add_role(user, role) - - sign_in(user) - end - - context 'when user is the owner' do - before do - project.namespace.update(owner: user) - end - - context 'when pages deployed' do - before do - allow_any_instance_of(Project).to receive(:pages_deployed?) { true } - end - - it 'renders Access pages' do - visit project_pages_path(project) - - expect(page).to have_content('Access pages') - end - - context 'when pages are disabled in the project settings' do - it 'renders disabled warning' do - project.project_feature.update!(pages_access_level: ProjectFeature::DISABLED) - - visit project_pages_path(project) - - expect(page).to have_content('GitLab Pages are disabled for this project') - end - end - - it 'renders first deployment warning' do - visit project_pages_path(project) - - expect(page).to have_content('It may take up to 30 minutes before the site is available after the first deployment.') - end - - shared_examples 'does not render access control warning' do - it 'does not render access control warning' do - visit project_pages_path(project) - - expect(page).not_to have_content('Access Control is enabled for this Pages website') - end - end - - include_examples 'does not render access control warning' - - context 'when access control is enabled in gitlab settings' do - before do - stub_pages_setting(access_control: true) - end - - it 'renders access control warning' do - visit project_pages_path(project) - - expect(page).to have_content('Access Control is enabled for this Pages website') - end - - context 'when pages are public' do - before do - project.project_feature.update!(pages_access_level: ProjectFeature::PUBLIC) - end - - include_examples 'does not render access control warning' - end - end - - context 'when support for external domains is disabled' do - it 'renders message that support is disabled' do - visit project_pages_path(project) - - expect(page).to have_content('Support for domains and certificates is disabled') - end - end - - context 'when pages are exposed on external HTTP address', :http_pages_enabled do - let(:project) { create(:project, pages_https_only: false) } - - shared_examples 'adds new domain' do - it 'adds new domain' do - visit new_project_pages_domain_path(project) - - fill_in 'Domain', with: 'my.test.domain.com' - click_button 'Create New Domain' - - expect(page).to have_content('my.test.domain.com') - end - end - - it 'allows to add new domain' do - visit project_pages_path(project) - - expect(page).to have_content('New Domain') - end - - it_behaves_like 'adds new domain' - - context 'when project in group namespace' do - it_behaves_like 'adds new domain' do - let(:group) { create :group } - let(:project) { create(:project, namespace: group, pages_https_only: false) } - end - end - - context 'when pages domain is added' do - before do - create(:pages_domain, project: project, domain: 'my.test.domain.com') - - visit new_project_pages_domain_path(project) - end - - it 'renders certificates is disabled' do - expect(page).to have_content('Support for custom certificates is disabled') - end - - it 'does not adds new domain and renders error message' do - fill_in 'Domain', with: 'my.test.domain.com' - click_button 'Create New Domain' - - expect(page).to have_content('Domain has already been taken') - end - end - end - - context 'when pages are exposed on external HTTPS address', :https_pages_enabled, :js do - let(:certificate_pem) do - attributes_for(:pages_domain)[:certificate] - end - - let(:certificate_key) do - attributes_for(:pages_domain)[:key] - end - - it 'adds new domain with certificate' do - visit new_project_pages_domain_path(project) - - fill_in 'Domain', with: 'my.test.domain.com' - - if ::Gitlab::LetsEncrypt.enabled? - find('.js-auto-ssl-toggle-container .project-feature-toggle').click - end - - fill_in 'Certificate (PEM)', with: certificate_pem - fill_in 'Key (PEM)', with: certificate_key - click_button 'Create New Domain' - - expect(page).to have_content('my.test.domain.com') - end - - it 'shows validation error if domain is duplicated' do - project.pages_domains.create!(domain: 'my.test.domain.com') - - visit new_project_pages_domain_path(project) - - fill_in 'Domain', with: 'my.test.domain.com' - click_button 'Create New Domain' - - expect(page).to have_content('Domain has already been taken') - end - - describe 'with dns verification enabled' do - before do - stub_application_setting(pages_domain_verification_enabled: true) - end - - it 'shows the DNS verification record' do - domain = create(:pages_domain, project: project) - - visit project_pages_path(project) - - within('#content-body') { click_link 'Edit' } - expect(page).to have_field :domain_verification, with: "#{domain.verification_domain} TXT #{domain.keyed_verification_code}" - end - end - - describe 'updating the certificate for an existing domain' do - let!(:domain) do - create(:pages_domain, project: project, auto_ssl_enabled: false) - end - - it 'allows the certificate to be updated' do - visit project_pages_path(project) - - within('#content-body') { click_link 'Edit' } - click_button 'Save Changes' - - expect(page).to have_content('Domain was updated') - end - - context 'when the certificate is invalid' do - let!(:domain) do - create(:pages_domain, :without_certificate, :without_key, project: project) - end - - it 'tells the user what the problem is' do - visit project_pages_path(project) - - within('#content-body') { click_link 'Edit' } - - if ::Gitlab::LetsEncrypt.enabled? - find('.js-auto-ssl-toggle-container .project-feature-toggle').click - end - - fill_in 'Certificate (PEM)', with: 'invalid data' - click_button 'Save Changes' - - expect(page).to have_content('Certificate must be a valid PEM certificate') - expect(page).to have_content('Certificate misses intermediates') - expect(page).to have_content("Key doesn't match the certificate") - end - end - - it 'allows the certificate to be removed', :js do - visit project_pages_path(project) - - within('#content-body') { click_link 'Edit' } - - accept_confirm { click_link 'Remove' } - - expect(page).to have_field('Certificate (PEM)', with: '') - expect(page).to have_field('Key (PEM)', with: '') - domain.reload - expect(domain.certificate).to be_nil - expect(domain.key).to be_nil - end - - it 'shows the DNS CNAME record' do - visit project_pages_path(project) - - within('#content-body') { click_link 'Edit' } - expect(page).to have_field :domain_dns, with: "#{domain.domain} CNAME #{domain.project.pages_subdomain}.#{Settings.pages.host}." - end - end - end - end - - it 'does not see anything to destroy' do - visit project_pages_path(project) - - expect(page).to have_content('Configure pages') - expect(page).not_to have_link('Remove pages') - end - - describe 'project settings page' do - it 'renders "Pages" tab' do - visit edit_project_path(project) - - page.within '.nav-sidebar' do - expect(page).to have_link('Pages') - end - end - - context 'when pages are disabled' do - before do - allow(Gitlab.config.pages).to receive(:enabled).and_return(false) - end - - it 'does not render "Pages" tab' do - visit edit_project_path(project) - - page.within '.nav-sidebar' do - expect(page).not_to have_link('Pages') - end - end - end - end - end - - describe 'HTTPS settings', :https_pages_enabled do - before do - project.namespace.update(owner: user) - - allow_any_instance_of(Project).to receive(:pages_deployed?) { true } - end - - it 'tries to change the setting' do - visit project_pages_path(project) - expect(page).to have_content("Force HTTPS (requires valid certificates)") - - uncheck :project_pages_https_only - - click_button 'Save' - - expect(page).to have_text('Your changes have been saved') - expect(page).not_to have_checked_field('project_pages_https_only') - end - - context 'setting could not be updated' do - let(:service) { instance_double('Projects::UpdateService') } - - before do - allow(Projects::UpdateService).to receive(:new).and_return(service) - allow(service).to receive(:execute).and_return(status: :error, message: 'Some error has occured') - end - - it 'tries to change the setting' do - visit project_pages_path(project) - - uncheck :project_pages_https_only - - click_button 'Save' - - expect(page).to have_text('Some error has occured') - end - end - - context 'non-HTTPS domain exists' do - let(:project) { create(:project, pages_https_only: false) } - - before do - create(:pages_domain, :without_key, :without_certificate, project: project) - end - - it 'the setting is disabled' do - visit project_pages_path(project) - - expect(page).to have_field(:project_pages_https_only, disabled: true) - expect(page).to have_button('Save') - end - end - - context 'HTTPS pages are disabled', :https_pages_disabled do - it 'the setting is unavailable' do - visit project_pages_path(project) - - expect(page).not_to have_field(:project_pages_https_only) - expect(page).not_to have_content('Force HTTPS (requires valid certificates)') - expect(page).to have_button('Save') - end - end - end - - describe 'Remove page' do - let(:project) { create :project, :repository } - - context 'when pages are deployed' do - let(:pipeline) do - commit_sha = project.commit('HEAD').sha - - project.ci_pipelines.create( - ref: 'HEAD', - sha: commit_sha, - source: :push, - protected: false - ) - end - - let(:ci_build) do - create( - :ci_build, - project: project, - pipeline: pipeline, - ref: 'HEAD') - end - - let!(:artifact) do - create(:ci_job_artifact, :archive, :correct_checksum, - file: fixture_file_upload(File.join('spec/fixtures/pages.zip')), job: ci_build) - end - - let!(:metadata) do - create(:ci_job_artifact, :metadata, - file: fixture_file_upload(File.join('spec/fixtures/pages.zip.meta')), job: ci_build) - end - - before do - result = Projects::UpdatePagesService.new(project, ci_build).execute - expect(result[:status]).to eq(:success) - expect(project).to be_pages_deployed - end - - it 'removes the pages', :sidekiq_inline do - visit project_pages_path(project) - - expect(page).to have_link('Remove pages') - - accept_confirm { click_link 'Remove pages' } - - expect(page).to have_content('Pages were scheduled for removal') - expect(project.reload.pages_deployed?).to be_falsey - end - end - end -end - -RSpec.describe 'Pages', :js do - include LetsEncryptHelpers - - context 'when editing normally' do - include_examples 'pages settings editing' - end - - context 'when letsencrypt support is enabled' do - before do - stub_lets_encrypt_settings - end - - include_examples 'pages settings editing' - end -end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index ac3566fbbdd..94800717677 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -14,7 +14,6 @@ RSpec.describe 'Pipeline', :js do before do sign_in(user) project.add_role(user, role) - stub_feature_flags(graphql_pipeline_details: false) end shared_context 'pipeline builds' do @@ -57,7 +56,7 @@ RSpec.describe 'Pipeline', :js do end end - describe 'GET /:project/pipelines/:id' do + describe 'GET /:project/-/pipelines/:id' do include_context 'pipeline builds' let(:group) { create(:group) } @@ -69,7 +68,7 @@ RSpec.describe 'Pipeline', :js do it 'shows the pipeline graph' do visit_pipeline - expect(page).to have_selector('.pipeline-visualization') + expect(page).to have_selector('.js-pipeline-graph') expect(page).to have_content('Build') expect(page).to have_content('Test') expect(page).to have_content('Deploy') @@ -625,20 +624,6 @@ RSpec.describe 'Pipeline', :js do end end end - - context 'when FF dag_pipeline_tab is disabled' do - before do - stub_feature_flags(dag_pipeline_tab: false) - visit_pipeline - end - - it 'does not show DAG link' do - expect(page).to have_link('Pipeline') - expect(page).to have_link('Jobs') - expect(page).not_to have_link('DAG') - expect(page).to have_link('Failed Jobs') - end - end end context 'when user does not have access to read jobs' do @@ -646,7 +631,7 @@ RSpec.describe 'Pipeline', :js do project.update(public_builds: false) end - describe 'GET /:project/pipelines/:id' do + describe 'GET /:project/-/pipelines/:id' do include_context 'pipeline builds' let(:project) { create(:project, :repository) } @@ -657,7 +642,7 @@ RSpec.describe 'Pipeline', :js do end it 'shows the pipeline graph' do - expect(page).to have_selector('.pipeline-visualization') + expect(page).to have_selector('.js-pipeline-graph') expect(page).to have_content('Build') expect(page).to have_content('Test') expect(page).to have_content('Deploy') @@ -691,13 +676,13 @@ RSpec.describe 'Pipeline', :js do downstream: downstream) end - describe 'GET /:project/pipelines/:id' do + describe 'GET /:project/-/pipelines/:id' do before do visit project_pipeline_path(project, pipeline) end it 'shows the pipeline with a bridge job' do - expect(page).to have_selector('.pipeline-visualization') + expect(page).to have_selector('.js-pipeline-graph') expect(page).to have_content('cross-build') end @@ -740,7 +725,7 @@ RSpec.describe 'Pipeline', :js do end end - describe 'GET /:project/pipelines/:id/builds' do + describe 'GET /:project/-/pipelines/:id/builds' do before do visit builds_project_pipeline_path(project, pipeline) end @@ -767,9 +752,64 @@ RSpec.describe 'Pipeline', :js do stage_idx: 2, pipeline: pipeline, project: project, resource_group: resource_group) end - describe 'GET /:project/pipelines/:id' do + describe 'GET /:project/-/pipelines/:id' do subject { visit project_pipeline_path(project, pipeline) } + # remove when :graphql_pipeline_details flag is removed + # https://gitlab.com/gitlab-org/gitlab/-/issues/299112 + context 'when :graphql_pipeline_details flag is off' do + before do + stub_feature_flags(graphql_pipeline_details: false) + stub_feature_flags(graphql_pipeline_details_users: false) + end + + it 'shows deploy job as created' do + subject + + within('.pipeline-header-container') do + expect(page).to have_content('pending') + end + + within('.js-pipeline-graph') do + within '.stage-column:nth-child(1)' do + expect(page).to have_content('test') + expect(page).to have_css('.ci-status-icon-pending') + end + + within '.stage-column:nth-child(2)' do + expect(page).to have_content('deploy') + expect(page).to have_css('.ci-status-icon-created') + end + end + end + + context 'when test job succeeded' do + before do + test_job.success! + end + + it 'shows deploy job as pending' do + subject + + within('.pipeline-header-container') do + expect(page).to have_content('running') + end + + within('.pipeline-graph') do + within '.stage-column:nth-child(1)' do + expect(page).to have_content('test') + expect(page).to have_css('.ci-status-icon-success') + end + + within '.stage-column:nth-child(2)' do + expect(page).to have_content('deploy') + expect(page).to have_css('.ci-status-icon-pending') + end + end + end + end + end + it 'shows deploy job as created' do subject @@ -777,13 +817,13 @@ RSpec.describe 'Pipeline', :js do expect(page).to have_content('pending') end - within('.pipeline-graph') do - within '.stage-column:nth-child(1)' do + within('.js-pipeline-graph') do + within(all('[data-testid="stage-column"]')[0]) do expect(page).to have_content('test') expect(page).to have_css('.ci-status-icon-pending') end - within '.stage-column:nth-child(2)' do + within(all('[data-testid="stage-column"]')[1]) do expect(page).to have_content('deploy') expect(page).to have_css('.ci-status-icon-created') end @@ -802,13 +842,13 @@ RSpec.describe 'Pipeline', :js do expect(page).to have_content('running') end - within('.pipeline-graph') do - within '.stage-column:nth-child(1)' do + within('.js-pipeline-graph') do + within(all('[data-testid="stage-column"]')[0]) do expect(page).to have_content('test') expect(page).to have_css('.ci-status-icon-success') end - within '.stage-column:nth-child(2)' do + within(all('[data-testid="stage-column"]')[1]) do expect(page).to have_content('deploy') expect(page).to have_css('.ci-status-icon-pending') end @@ -831,14 +871,37 @@ RSpec.describe 'Pipeline', :js do expect(page).to have_content('waiting') end - within('.pipeline-graph') do - within '.stage-column:nth-child(2)' do + within('.js-pipeline-graph') do + within(all('[data-testid="stage-column"]')[1]) do expect(page).to have_content('deploy') expect(page).to have_css('.ci-status-icon-waiting-for-resource') end end end + # remove when :graphql_pipeline_details flag is removed + # https://gitlab.com/gitlab-org/gitlab/-/issues/299112 + context 'when :graphql_pipeline_details flag is off' do + before do + stub_feature_flags(graphql_pipeline_details: false) + stub_feature_flags(graphql_pipeline_details_users: false) + end + it 'shows deploy job as waiting for resource' do + subject + + within('.pipeline-header-container') do + expect(page).to have_content('waiting') + end + + within('.pipeline-graph') do + within '.stage-column:nth-child(2)' do + expect(page).to have_content('deploy') + expect(page).to have_css('.ci-status-icon-waiting-for-resource') + end + end + end + end + context 'when resource is released from another job' do before do another_job.success! @@ -851,19 +914,86 @@ RSpec.describe 'Pipeline', :js do expect(page).to have_content('running') end - within('.pipeline-graph') do - within '.stage-column:nth-child(2)' do + within('.js-pipeline-graph') do + within(all('[data-testid="stage-column"]')[1]) do expect(page).to have_content('deploy') expect(page).to have_css('.ci-status-icon-pending') end end end + + # remove when :graphql_pipeline_details flag is removed + # https://gitlab.com/gitlab-org/gitlab/-/issues/299112 + context 'when :graphql_pipeline_details flag is off' do + before do + stub_feature_flags(graphql_pipeline_details: false) + stub_feature_flags(graphql_pipeline_details_users: false) + end + it 'shows deploy job as pending' do + subject + + within('.pipeline-header-container') do + expect(page).to have_content('running') + end + + within('.pipeline-graph') do + within '.stage-column:nth-child(2)' do + expect(page).to have_content('deploy') + expect(page).to have_css('.ci-status-icon-pending') + end + end + end + end + end + + context 'when deploy job is a bridge to trigger a downstream pipeline' do + let!(:deploy_job) do + create(:ci_bridge, :created, stage: 'deploy', name: 'deploy', + stage_idx: 2, pipeline: pipeline, project: project, resource_group: resource_group) + end + + it 'shows deploy job as waiting for resource' do + subject + + within('.pipeline-header-container') do + expect(page).to have_content('waiting') + end + + within('.js-pipeline-graph') do + within(all('[data-testid="stage-column"]')[1]) do + expect(page).to have_content('deploy') + expect(page).to have_css('.ci-status-icon-waiting-for-resource') + end + end + end + end + + context 'when deploy job is a bridge to trigger a downstream pipeline' do + let!(:deploy_job) do + create(:ci_bridge, :created, stage: 'deploy', name: 'deploy', + stage_idx: 2, pipeline: pipeline, project: project, resource_group: resource_group) + end + + it 'shows deploy job as waiting for resource' do + subject + + within('.pipeline-header-container') do + expect(page).to have_content('waiting') + end + + within('.js-pipeline-graph') do + within(all('[data-testid="stage-column"]')[1]) do + expect(page).to have_content('deploy') + expect(page).to have_css('.ci-status-icon-waiting-for-resource') + end + end + end end end end end - describe 'GET /:project/pipelines/:id/builds' do + describe 'GET /:project/-/pipelines/:id/builds' do include_context 'pipeline builds' let(:project) { create(:project, :repository) } @@ -965,7 +1095,7 @@ RSpec.describe 'Pipeline', :js do end end - describe 'GET /:project/pipelines/:id/failures' do + describe 'GET /:project/-/pipelines/:id/failures' do let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: '1234') } let(:pipeline_failures_page) { failures_project_pipeline_path(project, pipeline) } let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline) } @@ -1078,12 +1208,29 @@ RSpec.describe 'Pipeline', :js do expect(current_path).to eq(pipeline_path(pipeline)) expect(page).not_to have_content('Failed Jobs') - expect(page).to have_selector('.pipeline-visualization') + expect(page).to have_selector('.js-pipeline-graph') + end + + # remove when :graphql_pipeline_details flag is removed + # https://gitlab.com/gitlab-org/gitlab/-/issues/299112 + context 'when :graphql_pipeline_details flag is off' do + before do + stub_feature_flags(graphql_pipeline_details: false) + stub_feature_flags(graphql_pipeline_details_users: false) + end + + it 'displays the pipeline graph' do + subject + + expect(current_path).to eq(pipeline_path(pipeline)) + expect(page).not_to have_content('Failed Jobs') + expect(page).to have_selector('.pipeline-visualization') + end end end end - describe 'GET /:project/pipelines/:id/dag' do + describe 'GET /:project/-/pipelines/:id/dag' do include_context 'pipeline builds' let(:project) { create(:project, :repository) } diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 450524b8d70..6421d3db2cd 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -13,11 +13,13 @@ RSpec.describe 'Pipelines', :js do before do sign_in(user) stub_feature_flags(graphql_pipeline_details: false) + stub_feature_flags(graphql_pipeline_details_users: false) + project.add_developer(user) project.update!(auto_devops_attributes: { enabled: false }) end - describe 'GET /:project/pipelines' do + describe 'GET /:project/-/pipelines' do let(:project) { create(:project, :repository) } let!(:pipeline) do @@ -287,23 +289,23 @@ RSpec.describe 'Pipelines', :js do end it 'has a dropdown with play button' do - expect(page).to have_selector('.dropdown-new.btn.btn-default .icon-play') + expect(page).to have_selector('[data-testid="pipelines-manual-actions-dropdown"] [data-testid="play-icon"]') end it 'has link to the manual action' do - find('.js-pipeline-dropdown-manual-actions').click + find('[data-testid="pipelines-manual-actions-dropdown"]').click expect(page).to have_button('manual build') end context 'when manual action was played' do before do - find('.js-pipeline-dropdown-manual-actions').click + find('[data-testid="pipelines-manual-actions-dropdown"]').click click_button('manual build') end it 'enqueues manual action job' do - expect(page).to have_selector('.js-pipeline-dropdown-manual-actions:disabled') + expect(page).to have_selector('[data-testid="pipelines-manual-actions-dropdown"] .gl-dropdown-toggle:disabled') end end end @@ -321,11 +323,11 @@ RSpec.describe 'Pipelines', :js do end it 'has a dropdown for actionable jobs' do - expect(page).to have_selector('.dropdown-new.btn.btn-default .icon-play') + expect(page).to have_selector('[data-testid="pipelines-manual-actions-dropdown"] [data-testid="play-icon"]') end it "has link to the delayed job's action" do - find('.js-pipeline-dropdown-manual-actions').click + find('[data-testid="pipelines-manual-actions-dropdown"]').click time_diff = [0, delayed_job.scheduled_at - Time.now].max expect(page).to have_button('delayed job 1') @@ -341,7 +343,7 @@ RSpec.describe 'Pipelines', :js do end it "shows 00:00:00 as the remaining time" do - find('.js-pipeline-dropdown-manual-actions').click + find('[data-testid="pipelines-manual-actions-dropdown"]').click expect(page).to have_content("00:00:00") end @@ -349,7 +351,7 @@ RSpec.describe 'Pipelines', :js do context 'when user played a delayed job immediately' do before do - find('.js-pipeline-dropdown-manual-actions').click + find('[data-testid="pipelines-manual-actions-dropdown"]').click page.accept_confirm { click_button('delayed job 1') } wait_for_requests end @@ -517,56 +519,75 @@ RSpec.describe 'Pipelines', :js do end end - context 'mini pipeline graph' do - let!(:build) do - create(:ci_build, :pending, pipeline: pipeline, - stage: 'build', - name: 'build') - end - - before do - visit_project_pipelines - end + shared_examples 'mini pipeline renders' do |ci_mini_pipeline_gl_dropdown_enabled| + context 'mini pipeline graph' do + let!(:build) do + create(:ci_build, :pending, pipeline: pipeline, + stage: 'build', + name: 'build') + end - it 'renders a mini pipeline graph' do - expect(page).to have_selector('[data-testid="widget-mini-pipeline-graph"]') - expect(page).to have_selector('.js-builds-dropdown-button') - end + before do + stub_feature_flags(ci_mini_pipeline_gl_dropdown: ci_mini_pipeline_gl_dropdown_enabled) + visit_project_pipelines + end - context 'when clicking a stage badge' do - it 'opens a dropdown' do - find('.js-builds-dropdown-button').click + let_it_be(:dropdown_toggle_selector) do + if ci_mini_pipeline_gl_dropdown_enabled + '[data-testid="mini-pipeline-graph-dropdown"] .dropdown-toggle' + else + '[data-testid="mini-pipeline-graph-dropdown-toggle"]' + end + end - expect(page).to have_link build.name + it 'renders a mini pipeline graph' do + expect(page).to have_selector('[data-testid="widget-mini-pipeline-graph"]') + expect(page).to have_selector(dropdown_toggle_selector) end - it 'is possible to cancel pending build' do - find('.js-builds-dropdown-button').click - find('.js-ci-action').click - wait_for_requests + context 'when clicking a stage badge' do + it 'opens a dropdown' do + find(dropdown_toggle_selector).click - expect(build.reload).to be_canceled - end - end + expect(page).to have_link build.name + end - context 'for a failed pipeline' do - let!(:build) do - create(:ci_build, :failed, pipeline: pipeline, - stage: 'build', - name: 'build') + it 'is possible to cancel pending build' do + find(dropdown_toggle_selector).click + find('.js-ci-action').click + wait_for_requests + + expect(build.reload).to be_canceled + end end - it 'displays the failure reason' do - find('.js-builds-dropdown-button').click + context 'for a failed pipeline' do + let!(:build) do + create(:ci_build, :failed, pipeline: pipeline, + stage: 'build', + name: 'build') + end + + it 'displays the failure reason' do + find(dropdown_toggle_selector).click - within('.js-builds-dropdown-list') do - build_element = page.find('.mini-pipeline-graph-dropdown-item') - expect(build_element['title']).to eq('build - failed - (unknown failure)') + within('.js-builds-dropdown-list') do + build_element = page.find('.mini-pipeline-graph-dropdown-item') + expect(build_element['title']).to eq('build - failed - (unknown failure)') + end end end end end + context 'with ci_mini_pipeline_gl_dropdown disabled' do + it_behaves_like "mini pipeline renders", false + end + + context 'with ci_mini_pipeline_gl_dropdown enabled' do + it_behaves_like "mini pipeline renders", true + end + context 'with pagination' do before do allow(Ci::Pipeline).to receive(:default_per_page).and_return(1) @@ -597,7 +618,7 @@ RSpec.describe 'Pipelines', :js do end end - describe 'GET /:project/pipelines/show' do + describe 'GET /:project/-/pipelines/show' do let(:project) { create(:project, :repository) } let(:pipeline) do @@ -649,7 +670,7 @@ RSpec.describe 'Pipelines', :js do end end - describe 'POST /:project/pipelines' do + describe 'POST /:project/-/pipelines' do let(:project) { create(:project, :repository) } before do diff --git a/spec/features/projects/services/user_activates_slack_slash_command_spec.rb b/spec/features/projects/services/user_activates_slack_slash_command_spec.rb index 3994f55caee..4dfd4416eeb 100644 --- a/spec/features/projects/services/user_activates_slack_slash_command_spec.rb +++ b/spec/features/projects/services/user_activates_slack_slash_command_spec.rb @@ -40,4 +40,8 @@ RSpec.describe 'Slack slash commands', :js do value = find_field('url').value expect(value).to match("api/v4/projects/#{project.id}/services/slack_slash_commands/trigger") end + + it 'shows help content' do + expect(page).to have_content('This service allows users to perform common operations on this project by entering slash commands in Slack.') + end end diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb index c087237fd7c..39c4315bf0f 100644 --- a/spec/features/projects/settings/pipelines_settings_spec.rb +++ b/spec/features/projects/settings/pipelines_settings_spec.rb @@ -46,7 +46,7 @@ RSpec.describe "Projects > Settings > Pipelines settings" do it 'updates auto_cancel_pending_pipelines' do visit project_settings_ci_cd_path(project) - page.check('Auto-cancel redundant, pending pipelines') + page.check('Auto-cancel redundant pipelines') page.within '#js-general-pipeline-settings' do click_on 'Save changes' end diff --git a/spec/features/projects/settings/project_settings_spec.rb b/spec/features/projects/settings/project_settings_spec.rb index 7b2b5594c22..cd1c9ecde9c 100644 --- a/spec/features/projects/settings/project_settings_spec.rb +++ b/spec/features/projects/settings/project_settings_spec.rb @@ -6,7 +6,7 @@ RSpec.describe 'Projects settings' do let_it_be(:project) { create(:project) } let(:user) { project.owner } let(:panel) { find('.general-settings', match: :first) } - let(:button) { panel.find('.btn.js-settings-toggle') } + let(:button) { panel.find('.btn.gl-button.js-settings-toggle') } let(:title) { panel.find('.settings-title') } before do @@ -39,7 +39,7 @@ RSpec.describe 'Projects settings' do visit edit_project_path(project) forking_enabled_input = find('input[name="project[project_feature_attributes][forking_access_level]"]', visible: :hidden) - forking_enabled_button = find('input[name="project[project_feature_attributes][forking_access_level]"] + label > button') + forking_enabled_button = find('[data-for="project[project_feature_attributes][forking_access_level]"] .gl-toggle') expect(forking_enabled_input.value).to eq('20') diff --git a/spec/features/projects/settings/registry_settings_spec.rb b/spec/features/projects/settings/registry_settings_spec.rb index 2b03ecf5af1..6e4082d1391 100644 --- a/spec/features/projects/settings/registry_settings_spec.rb +++ b/spec/features/projects/settings/registry_settings_spec.rb @@ -39,7 +39,7 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p select('7 days', from: 'Remove tags older than:') fill_in('Remove tags matching:', with: '.*-production') - submit_button = find('.btn.btn-success') + submit_button = find('.btn.gl-button.btn-success') expect(submit_button).not_to be_disabled submit_button.click end @@ -53,7 +53,7 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p within '#js-registry-policies' do fill_in('Remove tags matching:', with: '*-production') - submit_button = find('.btn.btn-success') + submit_button = find('.btn.gl-button.btn-success') expect(submit_button).not_to be_disabled submit_button.click end diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb index 3e520142117..2f257d299d8 100644 --- a/spec/features/projects/settings/repository_settings_spec.rb +++ b/spec/features/projects/settings/repository_settings_spec.rb @@ -63,7 +63,7 @@ RSpec.describe 'Projects > Settings > Repository settings' do click_button 'Add key' expect(page).to have_content('new_deploy_key') - expect(page).to have_content('Write access allowed') + expect(page).to have_content('Grant write permissions to this key') end it 'edit an existing deploy key' do @@ -77,7 +77,7 @@ RSpec.describe 'Projects > Settings > Repository settings' do click_button 'Save changes' expect(page).to have_content('updated_deploy_key') - expect(page).to have_content('Write access allowed') + expect(page).to have_content('Grant write permissions to this key') end it 'edit an existing public deploy key to be writable' do @@ -90,7 +90,7 @@ RSpec.describe 'Projects > Settings > Repository settings' do click_button 'Save changes' expect(page).to have_content('public_deploy_key') - expect(page).to have_content('Write access allowed') + expect(page).to have_content('Grant write permissions to this key') end it 'edit a deploy key from projects user has access to' do diff --git a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb index e97e4a2030a..e8e32d93f7b 100644 --- a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb +++ b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb @@ -51,7 +51,7 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do expect(page).to have_content 'All discussions must be resolved' within('.sharing-permissions-form') do - find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .project-feature-toggle').click + find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .gl-toggle').click find('input[value="Save changes"]').send_keys(:return) end @@ -71,7 +71,7 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do expect(page).to have_content 'All discussions must be resolved' within('.sharing-permissions-form') do - find('.project-feature-controls[data-for="project[project_feature_attributes][builds_access_level]"] .project-feature-toggle').click + find('.project-feature-controls[data-for="project[project_feature_attributes][builds_access_level]"] .gl-toggle').click find('input[value="Save changes"]').send_keys(:return) end @@ -92,7 +92,7 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do expect(page).not_to have_content 'All discussions must be resolved' within('.sharing-permissions-form') do - find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .project-feature-toggle').click + find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .gl-toggle').click find('input[value="Save changes"]').send_keys(:return) end diff --git a/spec/features/projects/settings/user_manages_project_members_spec.rb b/spec/features/projects/settings/user_manages_project_members_spec.rb index 726b8fb6840..0d22da34b91 100644 --- a/spec/features/projects/settings/user_manages_project_members_spec.rb +++ b/spec/features/projects/settings/user_manages_project_members_spec.rb @@ -3,6 +3,9 @@ require 'spec_helper' RSpec.describe 'Projects > Settings > User manages project members' do + include Spec::Support::Helpers::Features::MembersHelpers + include Select2Helper + let(:group) { create(:group, name: 'OpenSource') } let(:project) { create(:project) } let(:project2) { create(:project) } @@ -16,62 +19,123 @@ RSpec.describe 'Projects > Settings > User manages project members' do sign_in(user) end - it 'cancels a team member', :js do - visit(project_project_members_path(project)) + context 'when `vue_project_members_list` feature flag is enabled' do + it 'cancels a team member', :js do + visit(project_project_members_path(project)) + + page.within find_member_row(user_dmitriy) do + click_button 'Remove member' + end + + page.within('[role="dialog"]') do + expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests' + click_button('Remove member') + end + + visit(project_project_members_path(project)) + + expect(members_table).not_to have_content(user_dmitriy.name) + expect(members_table).not_to have_content(user_dmitriy.username) + end + + it 'imports a team from another project', :js do + stub_feature_flags(invite_members_group_modal: false) + + project2.add_maintainer(user) + project2.add_reporter(user_mike) + + visit(project_project_members_path(project)) - project_member = project.project_members.find_by(user_id: user_dmitriy.id) + page.within('.invite-users-form') do + click_link('Import') + end - page.within("#project_member_#{project_member.id}") do - # Open modal - click_on('Remove user from project') + select2(project2.id, from: '#source_project_id') + click_button('Import project members') + + expect(find_member_row(user_mike)).to have_content('Reporter') end - expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests' + it 'shows all members of project shared group', :js do + group.add_owner(user) + group.add_developer(user_dmitriy) + + share_link = project.project_group_links.new(group_access: Gitlab::Access::MAINTAINER) + share_link.group_id = group.id + share_link.save! - click_on('Remove member') + visit(project_project_members_path(project)) - visit(project_project_members_path(project)) + click_link 'Groups' - expect(page).not_to have_content(user_dmitriy.name) - expect(page).not_to have_content(user_dmitriy.username) + expect(find_group_row(group)).to have_content('Maintainer') + end end - it 'imports a team from another project' do - project2.add_maintainer(user) - project2.add_reporter(user_mike) + context 'when `vue_project_members_list` feature flag is disabled' do + before do + stub_feature_flags(vue_project_members_list: false) + end + + it 'cancels a team member', :js do + visit(project_project_members_path(project)) + + project_member = project.project_members.find_by(user_id: user_dmitriy.id) + + page.within("#project_member_#{project_member.id}") do + # Open modal + click_on('Remove user from project') + end + + expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests' + + click_on('Remove member') - visit(project_project_members_path(project)) + visit(project_project_members_path(project)) - page.within('.invite-users-form') do - click_link('Import') + expect(page).not_to have_content(user_dmitriy.name) + expect(page).not_to have_content(user_dmitriy.username) end - select(project2.full_name, from: 'source_project_id') - click_button('Import') + it 'imports a team from another project' do + stub_feature_flags(invite_members_group_modal: false) - project_member = project.project_members.find_by(user_id: user_mike.id) + project2.add_maintainer(user) + project2.add_reporter(user_mike) - page.within("#project_member_#{project_member.id}") do - expect(page).to have_content('Mike') - expect(page).to have_content('Reporter') + visit(project_project_members_path(project)) + + page.within('.invite-users-form') do + click_link('Import') + end + + select(project2.full_name, from: 'source_project_id') + click_button('Import') + + project_member = project.project_members.find_by(user_id: user_mike.id) + + page.within("#project_member_#{project_member.id}") do + expect(page).to have_content('Mike') + expect(page).to have_content('Reporter') + end end - end - it 'shows all members of project shared group', :js do - group.add_owner(user) - group.add_developer(user_dmitriy) + it 'shows all members of project shared group', :js do + group.add_owner(user) + group.add_developer(user_dmitriy) - share_link = project.project_group_links.new(group_access: Gitlab::Access::MAINTAINER) - share_link.group_id = group.id - share_link.save! + share_link = project.project_group_links.new(group_access: Gitlab::Access::MAINTAINER) + share_link.group_id = group.id + share_link.save! - visit(project_project_members_path(project)) + visit(project_project_members_path(project)) - click_link 'Groups' + click_link 'Groups' - page.within('[data-testid="project-member-groups"]') do - expect(page).to have_content('OpenSource') - expect(first('.group_member')).to have_content('Maintainer') + page.within('[data-testid="project-member-groups"]') do + expect(page).to have_content('OpenSource') + expect(first('.group_member')).to have_content('Maintainer') + end end end end diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb index 6cecbbdb3d0..becb30c02b7 100644 --- a/spec/features/projects/settings/visibility_settings_spec.rb +++ b/spec/features/projects/settings/visibility_settings_spec.rb @@ -30,7 +30,7 @@ RSpec.describe 'Projects > Settings > Visibility settings', :js do context 'merge requests select' do it 'hides merge requests section' do - find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .project-feature-toggle').click + find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .gl-toggle').click expect(page).to have_selector('.merge-requests-feature', visible: false) end @@ -46,7 +46,7 @@ RSpec.describe 'Projects > Settings > Visibility settings', :js do context 'builds select' do it 'hides builds select section' do - find('.project-feature-controls[data-for="project[project_feature_attributes][builds_access_level]"] .project-feature-toggle').click + find('.project-feature-controls[data-for="project[project_feature_attributes][builds_access_level]"] .gl-toggle').click expect(page).to have_selector('.builds-feature', visible: false) end diff --git a/spec/features/projects/show/user_manages_notifications_spec.rb b/spec/features/projects/show/user_manages_notifications_spec.rb index d444ea27d35..5f7d9b0963b 100644 --- a/spec/features/projects/show/user_manages_notifications_spec.rb +++ b/spec/features/projects/show/user_manages_notifications_spec.rb @@ -6,6 +6,7 @@ RSpec.describe 'Projects > Show > User manages notifications', :js do let(:project) { create(:project, :public, :repository) } before do + stub_feature_flags(vue_notification_dropdown: false) sign_in(project.owner) end diff --git a/spec/features/projects/show/user_sees_git_instructions_spec.rb b/spec/features/projects/show/user_sees_git_instructions_spec.rb index febdb70de86..e6157887c12 100644 --- a/spec/features/projects/show/user_sees_git_instructions_spec.rb +++ b/spec/features/projects/show/user_sees_git_instructions_spec.rb @@ -5,6 +5,13 @@ require 'spec_helper' RSpec.describe 'Projects > Show > User sees Git instructions' do let_it_be(:user) { create(:user) } + before do + # Reset user notification settings between examples to prevent + # validation failure on NotificationSetting. + # See https://gitlab.com/gitlab-org/gitlab/-/issues/299822#note_492817174 + user.notification_settings.reset + end + shared_examples_for 'redirects to the sign in page' do it 'redirects to the sign in page' do expect(current_path).to eq(new_user_session_path) diff --git a/spec/features/projects/terraform_spec.rb b/spec/features/projects/terraform_spec.rb index dfa4dad8490..55b906c2bc5 100644 --- a/spec/features/projects/terraform_spec.rb +++ b/spec/features/projects/terraform_spec.rb @@ -68,7 +68,7 @@ RSpec.describe 'Terraform', :js do fill_in "terraform-state-remove-input-#{additional_state.name}", with: additional_state.name click_button 'Remove' - expect(page).not_to have_content(additional_state.name) + expect(page).to have_content("#{additional_state.name} successfully removed") expect { additional_state.reload }.to raise_error ActiveRecord::RecordNotFound end end diff --git a/spec/features/projects/user_creates_project_spec.rb b/spec/features/projects/user_creates_project_spec.rb index feb5f348256..aff3022bd4e 100644 --- a/spec/features/projects/user_creates_project_spec.rb +++ b/spec/features/projects/user_creates_project_spec.rb @@ -8,6 +8,8 @@ RSpec.describe 'User creates a project', :js do before do sign_in(user) create(:personal_key, user: user) + + stub_experiments(new_project_readme: :candidate) end it 'creates a new project' do @@ -16,6 +18,10 @@ RSpec.describe 'User creates a project', :js do find('[data-qa-selector="blank_project_link"]').click fill_in(:project_name, with: 'Empty') + # part of the new_project_readme experiment + expect(page).to have_checked_field 'Initialize repository with a README' + uncheck 'Initialize repository with a README' + page.within('#content-body') do click_button('Create project') end diff --git a/spec/features/projects/user_sees_user_popover_spec.rb b/spec/features/projects/user_sees_user_popover_spec.rb index 9cfc6234969..52e65deae3b 100644 --- a/spec/features/projects/user_sees_user_popover_spec.rb +++ b/spec/features/projects/user_sees_user_popover_spec.rb @@ -38,8 +38,6 @@ RSpec.describe 'User sees user popover', :js do it "displays user popover in system note" do add_note("/assign @#{user.username}") - wait_for_requests - find('.system-note-message .js-user-link').hover page.within(popover_selector) do diff --git a/spec/features/projects/user_uses_shortcuts_spec.rb b/spec/features/projects/user_uses_shortcuts_spec.rb index 8fa5f741a95..13ae035e8ef 100644 --- a/spec/features/projects/user_uses_shortcuts_spec.rb +++ b/spec/features/projects/user_uses_shortcuts_spec.rb @@ -27,14 +27,13 @@ RSpec.describe 'User uses shortcuts', :js do open_modal_shortcut_keys - # modal-shortcuts still in the DOM, but hidden - expect(find('#modal-shortcuts', visible: false)).not_to be_visible + expect(page).not_to have_selector('[data-testid="modal-shortcuts"]') page.refresh open_modal_shortcut_keys # after reload, shortcuts modal doesn't exist at all until we add it - expect(page).not_to have_selector('#modal-shortcuts') + expect(page).not_to have_selector('[data-testid="modal-shortcuts"]') end it 're-enables shortcuts' do @@ -47,7 +46,7 @@ RSpec.describe 'User uses shortcuts', :js do close_modal open_modal_shortcut_keys - expect(find('#modal-shortcuts')).to be_visible + expect(find('[data-testid="modal-shortcuts"]')).to be_visible end def open_modal_shortcut_keys |