diff options
Diffstat (limited to 'spec/features')
31 files changed, 1277 insertions, 231 deletions
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb index 7bbe20fec43..a6198389f04 100644 --- a/spec/features/admin/admin_builds_spec.rb +++ b/spec/features/admin/admin_builds_spec.rb @@ -6,15 +6,15 @@ describe 'Admin Builds' do end describe 'GET /admin/builds' do - let(:commit) { create(:ci_commit) } + let(:pipeline) { create(:ci_pipeline) } context 'All tab' do context 'when have builds' do it 'shows all builds' do - create(:ci_build, commit: commit, status: :pending) - create(:ci_build, commit: commit, status: :running) - create(:ci_build, commit: commit, status: :success) - create(:ci_build, commit: commit, status: :failed) + create(:ci_build, pipeline: pipeline, status: :pending) + create(:ci_build, pipeline: pipeline, status: :running) + create(:ci_build, pipeline: pipeline, status: :success) + create(:ci_build, pipeline: pipeline, status: :failed) visit admin_builds_path @@ -39,9 +39,9 @@ describe 'Admin Builds' do context 'Running tab' do context 'when have running builds' do it 'shows running builds' do - build1 = create(:ci_build, commit: commit, status: :pending) - build2 = create(:ci_build, commit: commit, status: :success) - build3 = create(:ci_build, commit: commit, status: :failed) + build1 = create(:ci_build, pipeline: pipeline, status: :pending) + build2 = create(:ci_build, pipeline: pipeline, status: :success) + build3 = create(:ci_build, pipeline: pipeline, status: :failed) visit admin_builds_path(scope: :running) @@ -55,7 +55,7 @@ describe 'Admin Builds' do context 'when have no builds running' do it 'shows a message' do - create(:ci_build, commit: commit, status: :success) + create(:ci_build, pipeline: pipeline, status: :success) visit admin_builds_path(scope: :running) @@ -69,9 +69,9 @@ describe 'Admin Builds' do context 'Finished tab' do context 'when have finished builds' do it 'shows finished builds' do - build1 = create(:ci_build, commit: commit, status: :pending) - build2 = create(:ci_build, commit: commit, status: :running) - build3 = create(:ci_build, commit: commit, status: :success) + build1 = create(:ci_build, pipeline: pipeline, status: :pending) + build2 = create(:ci_build, pipeline: pipeline, status: :running) + build3 = create(:ci_build, pipeline: pipeline, status: :success) visit admin_builds_path(scope: :finished) @@ -85,7 +85,7 @@ describe 'Admin Builds' do context 'when have no builds finished' do it 'shows a message' do - create(:ci_build, commit: commit, status: :running) + create(:ci_build, pipeline: pipeline, status: :running) visit admin_builds_path(scope: :finished) diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index 8ebd4a6808e..9499cd4e025 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -8,8 +8,8 @@ describe "Admin Runners" do describe "Runners page" do before do runner = FactoryGirl.create(:ci_runner) - commit = FactoryGirl.create(:ci_commit) - FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) + pipeline = FactoryGirl.create(:ci_pipeline) + FactoryGirl.create(:ci_build, pipeline: pipeline, runner_id: runner.id) visit admin_runners_path end diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index 96621843b30..1cb709c1de3 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -19,7 +19,7 @@ describe "Admin::Users", feature: true do describe 'Two-factor Authentication filters' do it 'counts users who have enabled 2FA' do - create(:user, two_factor_enabled: true) + create(:user, :two_factor) visit admin_users_path @@ -29,7 +29,7 @@ describe "Admin::Users", feature: true do end it 'filters by users who have enabled 2FA' do - user = create(:user, two_factor_enabled: true) + user = create(:user, :two_factor) visit admin_users_path click_link '2FA Enabled' @@ -38,7 +38,7 @@ describe "Admin::Users", feature: true do end it 'counts users who have not enabled 2FA' do - create(:user, two_factor_enabled: false) + create(:user) visit admin_users_path @@ -48,7 +48,7 @@ describe "Admin::Users", feature: true do end it 'filters by users who have not enabled 2FA' do - user = create(:user, two_factor_enabled: false) + user = create(:user) visit admin_users_path click_link '2FA Disabled' @@ -144,8 +144,8 @@ describe "Admin::Users", feature: true do before { click_link 'Impersonate' } it 'logs in as the user when impersonate is clicked' do - page.within '.sidebar-user .username' do - expect(page).to have_content(another_user.username) + page.within '.sidebar-wrapper' do + expect(page.find('.sidebar-user')['data-user']).to eql(another_user.username) end end @@ -158,8 +158,8 @@ describe "Admin::Users", feature: true do it 'can log out of impersonated user back to original user' do find(:css, 'li.impersonation a').click - page.within '.sidebar-user .username' do - expect(page).to have_content(@user.username) + page.within '.sidebar-wrapper' do + expect(page.find('.sidebar-user')['data-user']).to eql(@user.username) end end @@ -173,7 +173,7 @@ describe "Admin::Users", feature: true do describe 'Two-factor Authentication status' do it 'shows when enabled' do - @user.update_attribute(:two_factor_enabled, true) + @user.update_attribute(:otp_required_for_login, true) visit admin_user_path(@user) diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb index b710cb3c72f..4dd9548cfc5 100644 --- a/spec/features/atom/dashboard_issues_spec.rb +++ b/spec/features/atom/dashboard_issues_spec.rb @@ -5,8 +5,6 @@ describe "Dashboard Issues Feed", feature: true do let!(:user) { create(:user) } let!(:project1) { create(:project) } let!(:project2) { create(:project) } - let!(:issue1) { create(:issue, author: user, assignee: user, project: project1) } - let!(:issue2) { create(:issue, author: user, assignee: user, project: project2) } before do project1.team << [user, :master] @@ -14,16 +12,51 @@ describe "Dashboard Issues Feed", feature: true do end describe "atom feed" do - it "should render atom feed via private token" do + it "renders atom feed via private token" do visit issues_dashboard_path(:atom, private_token: user.private_token) - expect(response_headers['Content-Type']). - to have_content('application/atom+xml') + expect(response_headers['Content-Type']).to have_content('application/atom+xml') expect(body).to have_selector('title', text: "#{user.name} issues") - expect(body).to have_selector('author email', text: issue1.author_email) - expect(body).to have_selector('entry summary', text: issue1.title) - expect(body).to have_selector('author email', text: issue2.author_email) - expect(body).to have_selector('entry summary', text: issue2.title) + end + + context "issue with basic fields" do + let!(:issue2) { create(:issue, author: user, assignee: user, project: project2, description: 'test desc') } + + it "renders issue fields" do + visit issues_dashboard_path(:atom, private_token: user.private_token) + + entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]") + + expect(entry).to be_present + expect(entry).to have_selector('author email', text: issue2.author_email) + expect(entry).to have_selector('assignee email', text: issue2.author_email) + expect(entry).not_to have_selector('labels') + expect(entry).not_to have_selector('milestone') + expect(entry).to have_selector('description', text: issue2.description) + end + end + + context "issue with label and milestone" do + let!(:milestone1) { create(:milestone, project: project1, title: 'v1') } + let!(:label1) { create(:label, project: project1, title: 'label1') } + let!(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone1) } + + before do + issue1.labels << label1 + end + + it "renders issue label and milestone info" do + visit issues_dashboard_path(:atom, private_token: user.private_token) + + entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]") + + expect(entry).to be_present + expect(entry).to have_selector('author email', text: issue1.author_email) + expect(entry).to have_selector('assignee email', text: issue1.author_email) + expect(entry).to have_selector('labels label', text: label1.title) + expect(entry).to have_selector('milestone', text: milestone1.title) + expect(entry).not_to have_selector('description') + end end end end diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb index 7a05d30e8b5..b8ecc356b4d 100644 --- a/spec/features/builds_spec.rb +++ b/spec/features/builds_spec.rb @@ -5,8 +5,9 @@ describe "Builds" do before do login_as(:user) - @commit = FactoryGirl.create :ci_commit - @build = FactoryGirl.create :ci_build, commit: @commit + @commit = FactoryGirl.create :ci_pipeline + @build = FactoryGirl.create :ci_build, pipeline: @commit + @build2 = FactoryGirl.create :ci_build @project = @commit.project @project.team << [@user, :developer] end @@ -66,13 +67,24 @@ describe "Builds" do end describe "GET /:project/builds/:id" do - before do - visit namespace_project_build_path(@project.namespace, @project, @build) + context "Build from project" do + before do + visit namespace_project_build_path(@project.namespace, @project, @build) + end + + it { expect(page.status_code).to eq(200) } + it { expect(page).to have_content @commit.sha[0..7] } + it { expect(page).to have_content @commit.git_commit_message } + it { expect(page).to have_content @commit.git_author_name } end - it { expect(page).to have_content @commit.sha[0..7] } - it { expect(page).to have_content @commit.git_commit_message } - it { expect(page).to have_content @commit.git_author_name } + context "Build from other project" do + before do + visit namespace_project_build_path(@project.namespace, @project, @build2) + end + + it { expect(page.status_code).to eq(404) } + end context "Download artifacts" do before do @@ -81,9 +93,7 @@ describe "Builds" do end it 'has button to download artifacts' do - page.within('.artifacts') do - expect(page).to have_content 'Download' - end + expect(page).to have_content 'Download' end end @@ -95,59 +105,144 @@ describe "Builds" do end it do - page.within('.build-controls') do - expect(page).to have_link 'Raw' - end + expect(page).to have_link 'Raw' end end end describe "POST /:project/builds/:id/cancel" do - before do - @build.run! - visit namespace_project_build_path(@project.namespace, @project, @build) - click_link "Cancel" + context "Build from project" do + before do + @build.run! + visit namespace_project_build_path(@project.namespace, @project, @build) + click_link "Cancel" + end + + it { expect(page.status_code).to eq(200) } + it { expect(page).to have_content 'canceled' } + it { expect(page).to have_content 'Retry' } end - it { expect(page).to have_content 'canceled' } - it { expect(page).to have_content 'Retry' } + context "Build from other project" do + before do + @build.run! + visit namespace_project_build_path(@project.namespace, @project, @build) + page.driver.post(cancel_namespace_project_build_path(@project.namespace, @project, @build2)) + end + + it { expect(page.status_code).to eq(404) } + end end describe "POST /:project/builds/:id/retry" do - before do - @build.run! - visit namespace_project_build_path(@project.namespace, @project, @build) - click_link "Cancel" - click_link 'Retry' + context "Build from project" do + before do + @build.run! + visit namespace_project_build_path(@project.namespace, @project, @build) + click_link 'Cancel' + click_link 'Retry' + end + + it { expect(page.status_code).to eq(200) } + it { expect(page).to have_content 'pending' } + it { expect(page).to have_content 'Cancel' } end - it { expect(page).to have_content 'pending' } - it { expect(page).to have_content 'Cancel' } + context "Build from other project" do + before do + @build.run! + visit namespace_project_build_path(@project.namespace, @project, @build) + click_link 'Cancel' + page.driver.post(retry_namespace_project_build_path(@project.namespace, @project, @build2)) + end + + it { expect(page.status_code).to eq(404) } + end end describe "GET /:project/builds/:id/download" do before do @build.update_attributes(artifacts_file: artifacts_file) visit namespace_project_build_path(@project.namespace, @project, @build) - page.within('.artifacts') { click_link 'Download' } + click_link 'Download' end - it { expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type) } + context "Build from other project" do + before do + @build2.update_attributes(artifacts_file: artifacts_file) + visit download_namespace_project_build_artifacts_path(@project.namespace, @project, @build2) + end + + it { expect(page.status_code).to eq(404) } + end end describe "GET /:project/builds/:id/raw" do - before do - Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') - @build.run! - @build.trace = 'BUILD TRACE' - visit namespace_project_build_path(@project.namespace, @project, @build) + context "Build from project" do + before do + Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') + @build.run! + @build.trace = 'BUILD TRACE' + visit namespace_project_build_path(@project.namespace, @project, @build) + page.within('.js-build-sidebar') { click_link 'Raw' } + end + + it 'sends the right headers' do + expect(page.status_code).to eq(200) + expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8') + expect(page.response_headers['X-Sendfile']).to eq(@build.path_to_trace) + end end - it 'sends the right headers' do - page.within('.build-controls') { click_link 'Raw' } + context "Build from other project" do + before do + Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') + @build2.run! + @build2.trace = 'BUILD TRACE' + visit raw_namespace_project_build_path(@project.namespace, @project, @build2) + puts page.status_code + puts current_url + end + + it 'sends the right headers' do + expect(page.status_code).to eq(404) + end + end + end + + describe "GET /:project/builds/:id/trace.json" do + context "Build from project" do + before do + visit trace_namespace_project_build_path(@project.namespace, @project, @build, format: :json) + end + + it { expect(page.status_code).to eq(200) } + end + + context "Build from other project" do + before do + visit trace_namespace_project_build_path(@project.namespace, @project, @build2, format: :json) + end + + it { expect(page.status_code).to eq(404) } + end + end + + describe "GET /:project/builds/:id/status" do + context "Build from project" do + before do + visit status_namespace_project_build_path(@project.namespace, @project, @build) + end + + it { expect(page.status_code).to eq(200) } + end + + context "Build from other project" do + before do + visit status_namespace_project_build_path(@project.namespace, @project, @build2) + end - expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8') - expect(page.response_headers['X-Sendfile']).to eq(@build.path_to_trace) + it { expect(page.status_code).to eq(404) } end end end diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index 20f0b27bcc1..45e1a157a1f 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -8,15 +8,15 @@ describe 'Commits' do describe 'CI' do before do login_as :user - stub_ci_commit_to_return_yaml_file + stub_ci_pipeline_to_return_yaml_file end - let!(:commit) do - FactoryGirl.create :ci_commit, project: project, sha: project.commit.sha + let!(:pipeline) do + FactoryGirl.create :ci_pipeline, project: project, sha: project.commit.sha end context 'commit status is Generic Commit Status' do - let!(:status) { FactoryGirl.create :generic_commit_status, commit: commit } + let!(:status) { FactoryGirl.create :generic_commit_status, pipeline: pipeline } before do project.team << [@user, :reporter] @@ -24,10 +24,10 @@ describe 'Commits' do describe 'Commit builds' do before do - visit ci_status_path(commit) + visit ci_status_path(pipeline) end - it { expect(page).to have_content commit.sha[0..7] } + it { expect(page).to have_content pipeline.sha[0..7] } it 'contains generic commit status build' do page.within('.table-holder') do @@ -39,7 +39,7 @@ describe 'Commits' do end context 'commit status is Ci Build' do - let!(:build) { FactoryGirl.create :ci_build, commit: commit } + let!(:build) { FactoryGirl.create :ci_build, pipeline: pipeline } let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } context 'when logged as developer' do @@ -53,7 +53,7 @@ describe 'Commits' do end it 'should show build status' do - page.within("//li[@id='commit-#{commit.short_sha}']") do + page.within("//li[@id='commit-#{pipeline.short_sha}']") do expect(page).to have_css(".ci-status-link") end end @@ -61,12 +61,12 @@ describe 'Commits' do describe 'Commit builds' do before do - visit ci_status_path(commit) + visit ci_status_path(pipeline) end - it { expect(page).to have_content commit.sha[0..7] } - it { expect(page).to have_content commit.git_commit_message } - it { expect(page).to have_content commit.git_author_name } + it { expect(page).to have_content pipeline.sha[0..7] } + it { expect(page).to have_content pipeline.git_commit_message } + it { expect(page).to have_content pipeline.git_author_name } end context 'Download artifacts' do @@ -75,7 +75,7 @@ describe 'Commits' do end it do - visit ci_status_path(commit) + visit ci_status_path(pipeline) click_on 'Download artifacts' expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type) end @@ -83,7 +83,7 @@ describe 'Commits' do describe 'Cancel all builds' do it 'cancels commit' do - visit ci_status_path(commit) + visit ci_status_path(pipeline) click_on 'Cancel running' expect(page).to have_content 'canceled' end @@ -91,7 +91,7 @@ describe 'Commits' do describe 'Cancel build' do it 'cancels build' do - visit ci_status_path(commit) + visit ci_status_path(pipeline) click_on 'Cancel' expect(page).to have_content 'canceled' end @@ -100,13 +100,13 @@ describe 'Commits' do describe '.gitlab-ci.yml not found warning' do context 'ci builds enabled' do it "does not show warning" do - visit ci_status_path(commit) + visit ci_status_path(pipeline) expect(page).not_to have_content '.gitlab-ci.yml not found in this commit' end it 'shows warning' do - stub_ci_commit_yaml_file(nil) - visit ci_status_path(commit) + stub_ci_pipeline_yaml_file(nil) + visit ci_status_path(pipeline) expect(page).to have_content '.gitlab-ci.yml not found in this commit' end end @@ -114,8 +114,8 @@ describe 'Commits' do context 'ci builds disabled' do before do stub_ci_builds_disabled - stub_ci_commit_yaml_file(nil) - visit ci_status_path(commit) + stub_ci_pipeline_yaml_file(nil) + visit ci_status_path(pipeline) end it 'does not show warning' do @@ -129,13 +129,13 @@ describe 'Commits' do before do project.team << [@user, :reporter] build.update_attributes(artifacts_file: artifacts_file) - visit ci_status_path(commit) + visit ci_status_path(pipeline) end it do - expect(page).to have_content commit.sha[0..7] - expect(page).to have_content commit.git_commit_message - expect(page).to have_content commit.git_author_name + expect(page).to have_content pipeline.sha[0..7] + expect(page).to have_content pipeline.git_commit_message + expect(page).to have_content pipeline.git_author_name expect(page).to have_link('Download artifacts') expect(page).not_to have_link('Cancel running') expect(page).not_to have_link('Retry failed') @@ -148,13 +148,13 @@ describe 'Commits' do visibility_level: Gitlab::VisibilityLevel::INTERNAL, public_builds: false) build.update_attributes(artifacts_file: artifacts_file) - visit ci_status_path(commit) + visit ci_status_path(pipeline) end it do - expect(page).to have_content commit.sha[0..7] - expect(page).to have_content commit.git_commit_message - expect(page).to have_content commit.git_author_name + expect(page).to have_content pipeline.sha[0..7] + expect(page).to have_content pipeline.git_commit_message + expect(page).to have_content pipeline.git_author_name expect(page).not_to have_link('Download artifacts') expect(page).not_to have_link('Cancel running') expect(page).not_to have_link('Retry failed') diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb new file mode 100644 index 00000000000..365cb445df1 --- /dev/null +++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +feature 'Tooltips on .timeago dates', feature: true, js: true do + include WaitForAjax + + let(:user) { create(:user) } + let(:project) { create(:project, name: 'test', namespace: user.namespace) } + let(:created_date) { Date.yesterday.to_time } + let(:expected_format) { created_date.strftime('%b %-d, %Y %l:%M%P UTC') } + + context 'on the activity tab' do + before do + project.team << [user, :master] + + Event.create( project: project, author_id: user.id, action: Event::JOINED, + updated_at: created_date, created_at: created_date) + + login_as user + visit user_path(user) + wait_for_ajax() + + page.find('.js-timeago').hover + end + + it 'has the datetime formated correctly' do + expect(page).to have_selector('.local-timeago', text: expected_format) + end + end + + context 'on the snippets tab' do + before do + project.team << [user, :master] + create(:snippet, author: user, updated_at: created_date, created_at: created_date) + + login_as user + visit user_snippets_path(user) + wait_for_ajax() + + page.find('.js-timeago').hover + end + + it 'has the datetime formated correctly' do + expect(page).to have_selector('.local-timeago', text: expected_format) + end + end +end diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb index 41af789aae2..07a854ea014 100644 --- a/spec/features/issues/award_emoji_spec.rb +++ b/spec/features/issues/award_emoji_spec.rb @@ -28,7 +28,6 @@ describe 'Awards Emoji', feature: true do end context 'click the thumbsup emoji' do - it 'should increment the thumbsup emoji', js: true do find('[data-emoji="thumbsup"]').click sleep 2 @@ -41,7 +40,6 @@ describe 'Awards Emoji', feature: true do end context 'click the thumbsdown emoji' do - it 'should increment the thumbsdown emoji', js: true do find('[data-emoji="thumbsdown"]').click sleep 2 diff --git a/spec/features/issues/award_spec.rb b/spec/features/issues/award_spec.rb new file mode 100644 index 00000000000..63efecf8780 --- /dev/null +++ b/spec/features/issues/award_spec.rb @@ -0,0 +1,49 @@ +require 'rails_helper' + +feature 'Issue awards', js: true, feature: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project) } + + describe 'logged in' do + before do + login_as(user) + visit namespace_project_issue_path(project.namespace, project, issue) + end + + it 'should add award to issue' do + first('.js-emoji-btn').click + expect(page).to have_selector('.js-emoji-btn.active') + expect(first('.js-emoji-btn')).to have_content '1' + + visit namespace_project_issue_path(project.namespace, project, issue) + expect(first('.js-emoji-btn')).to have_content '1' + end + + it 'should remove award from issue' do + first('.js-emoji-btn').click + find('.js-emoji-btn.active').click + expect(first('.js-emoji-btn')).to have_content '0' + + visit namespace_project_issue_path(project.namespace, project, issue) + expect(first('.js-emoji-btn')).to have_content '0' + end + + it 'should only have one menu on the page' do + first('.js-add-award').click + expect(page).to have_selector('.emoji-menu') + + expect(page).to have_selector('.emoji-menu', count: 1) + end + end + + describe 'logged out' do + before do + visit namespace_project_issue_path(project.namespace, project, issue) + end + + it 'should not see award menu button' do + expect(page).not_to have_selector('.js-award-holder') + end + end +end diff --git a/spec/features/issues/bulk_assigment_labels_spec.rb b/spec/features/issues/bulk_assigment_labels_spec.rb new file mode 100644 index 00000000000..0fbc2062e39 --- /dev/null +++ b/spec/features/issues/bulk_assigment_labels_spec.rb @@ -0,0 +1,213 @@ +require 'rails_helper' + +feature 'Issues > Labels bulk assignment', feature: true do + include WaitForAjax + + let(:user) { create(:user) } + let!(:project) { create(:project) } + let!(:issue1) { create(:issue, project: project, title: "Issue 1") } + let!(:issue2) { create(:issue, project: project, title: "Issue 2") } + let!(:bug) { create(:label, project: project, title: 'bug') } + let!(:feature) { create(:label, project: project, title: 'feature') } + + context 'as a allowed user', js: true do + before do + project.team << [user, :master] + + login_as user + end + + context 'can bulk assign' do + before do + visit namespace_project_issues_path(project.namespace, project) + end + + context 'a label' do + context 'to all issues' do + before do + check 'check_all_issues' + open_labels_dropdown ['bug'] + update_issues + end + + it do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue2.id}")).to have_content 'bug' + end + end + + context 'to a issue' do + before do + check "selected_issue_#{issue1.id}" + open_labels_dropdown ['bug'] + update_issues + end + + it do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue2.id}")).not_to have_content 'bug' + end + end + end + + context 'multiple labels' do + context 'to all issues' do + before do + check 'check_all_issues' + open_labels_dropdown ['bug', 'feature'] + update_issues + end + + it do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'feature' + expect(find("#issue_#{issue2.id}")).to have_content 'bug' + expect(find("#issue_#{issue2.id}")).to have_content 'feature' + end + end + + context 'to a issue' do + before do + check "selected_issue_#{issue1.id}" + open_labels_dropdown ['bug', 'feature'] + update_issues + end + + it do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'feature' + expect(find("#issue_#{issue2.id}")).not_to have_content 'bug' + expect(find("#issue_#{issue2.id}")).not_to have_content 'feature' + end + end + end + end + + context 'can assign a label to all issues when label is present' do + before do + issue2.labels << bug + issue2.labels << feature + visit namespace_project_issues_path(project.namespace, project) + + check 'check_all_issues' + open_labels_dropdown ['bug'] + update_issues + end + + it do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue2.id}")).to have_content 'bug' + end + end + + context 'can bulk un-assign' do + context 'all labels to all issues' do + before do + issue1.labels << bug + issue1.labels << feature + issue2.labels << bug + issue2.labels << feature + + visit namespace_project_issues_path(project.namespace, project) + + check 'check_all_issues' + unmark_labels_in_dropdown ['bug', 'feature'] + update_issues + end + + it do + expect(find("#issue_#{issue1.id}")).not_to have_content 'bug' + expect(find("#issue_#{issue1.id}")).not_to have_content 'feature' + expect(find("#issue_#{issue2.id}")).not_to have_content 'bug' + expect(find("#issue_#{issue2.id}")).not_to have_content 'feature' + end + end + + context 'a label to a issue' do + before do + issue1.labels << bug + issue2.labels << feature + + visit namespace_project_issues_path(project.namespace, project) + + check_issue issue1 + unmark_labels_in_dropdown ['bug'] + update_issues + end + + it do + expect(find("#issue_#{issue1.id}")).not_to have_content 'bug' + expect(find("#issue_#{issue2.id}")).to have_content 'feature' + end + end + + context 'a label and keep the others label' do + before do + issue1.labels << bug + issue1.labels << feature + issue2.labels << bug + issue2.labels << feature + + visit namespace_project_issues_path(project.namespace, project) + + check_issue issue1 + check_issue issue2 + unmark_labels_in_dropdown ['bug'] + update_issues + end + + it do + expect(find("#issue_#{issue1.id}")).not_to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'feature' + expect(find("#issue_#{issue2.id}")).not_to have_content 'bug' + expect(find("#issue_#{issue2.id}")).to have_content 'feature' + end + end + end + end + + context 'as a guest' do + before do + login_as user + + visit namespace_project_issues_path(project.namespace, project) + end + + context 'cannot bulk assign labels' do + it do + expect(page).not_to have_css '.check_all_issues' + expect(page).not_to have_css '.issue-check' + end + end + end + + def open_labels_dropdown(items = [], unmark = false) + page.within('.issues_bulk_update') do + click_button 'Label' + wait_for_ajax + items.map do |item| + click_link item + end + if unmark + items.map do |item| + click_link item + end + end + end + end + + def unmark_labels_in_dropdown(items = []) + open_labels_dropdown(items, true) + end + + def check_issue(issue) + page.within('.issues-list') do + check "selected_issue_#{issue.id}" + end + end + + def update_issues + click_button 'Update issues' + wait_for_ajax + end +end diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb index 7f654684143..16c619c9288 100644 --- a/spec/features/issues/filter_by_labels_spec.rb +++ b/spec/features/issues/filter_by_labels_spec.rb @@ -54,6 +54,11 @@ feature 'Issue filtering by Labels', feature: true do expect(find('.filtered-labels')).not_to have_content "feature" expect(find('.filtered-labels')).not_to have_content "enhancement" end + + it 'should remove label "bug"' do + first('.js-label-filter-remove').click + expect(find('.filtered-labels')).to have_no_content "bug" + end end context 'filter by label feature', js: true do @@ -135,6 +140,11 @@ feature 'Issue filtering by Labels', feature: true do it 'should not show label "bug" in filtered-labels' do expect(find('.filtered-labels')).not_to have_content "bug" end + + it 'should remove label "enhancement"' do + first('.js-label-filter-remove').click + expect(find('.filtered-labels')).to have_no_content "enhancement" + end end context 'filter by label enhancement and bug in issues list', js: true do @@ -164,4 +174,44 @@ feature 'Issue filtering by Labels', feature: true do expect(find('.filtered-labels')).not_to have_content "feature" end end + + context 'remove filtered labels', js: true do + before do + page.within '.labels-filter' do + click_button 'Label' + click_link 'bug' + find('.dropdown-menu-close').click + end + + page.within '.filtered-labels' do + expect(page).to have_content 'bug' + end + end + + it 'should allow user to remove filtered labels' do + page.within '.filtered-labels' do + first('.js-label-filter-remove').click + expect(page).not_to have_content 'bug' + end + + page.within '.labels-filter' do + expect(page).not_to have_content 'bug' + end + end + end + + context 'dropdown filtering', js: true do + it 'should filter by label name' do + page.within '.labels-filter' do + click_button 'Label' + wait_for_ajax + fill_in 'label-name', with: 'bug' + + page.within '.dropdown-content' do + expect(page).not_to have_content 'enhancement' + expect(page).to have_content 'bug' + end + end + end + end end diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb index 7efbaaa048c..1f0594e6b02 100644 --- a/spec/features/issues/filter_issues_spec.rb +++ b/spec/features/issues/filter_issues_spec.rb @@ -294,40 +294,4 @@ describe 'Filter issues', feature: true do end end end - - describe 'filter by any author', js: true do - before do - user2 = create(:user, name: "tester") - create(:issue, project: project, author: user) - create(:issue, project: project, author: user2) - - visit namespace_project_issues_path(project.namespace, project) - end - - it 'should show filter by any author link' do - click_button "Author" - fill_in "Search authors", with: "tester" - - page.within ".dropdown-menu-author" do - expect(page).to have_content "tester" - end - end - - it 'should show filter issues by any author' do - page.within '.issues-list' do - expect(page).to have_selector ".issue", count: 2 - end - - click_button "Author" - fill_in "Search authors", with: "tester" - - page.within ".dropdown-menu-author" do - click_link "tester" - end - - page.within '.issues-list' do - expect(page).to have_selector ".issue", count: 1 - end - end - end end diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb index 466a6f7dfa7..ddbd69b2891 100644 --- a/spec/features/issues/update_issues_spec.rb +++ b/spec/features/issues/update_issues_spec.rb @@ -1,6 +1,8 @@ require 'rails_helper' feature 'Multiple issue updating from issues#index', feature: true do + include WaitForAjax + let!(:project) { create(:project) } let!(:issue) { create(:issue, project: project) } let!(:user) { create(:user)} @@ -24,9 +26,7 @@ feature 'Multiple issue updating from issues#index', feature: true do it 'should be set to open' do create_closed - visit namespace_project_issues_path(project.namespace, project) - - find('.issues-state-filters a', text: 'Closed').click + visit namespace_project_issues_path(project.namespace, project, state: 'closed') find('#check_all_issues').click find('.js-issue-status').click @@ -42,7 +42,7 @@ feature 'Multiple issue updating from issues#index', feature: true do visit namespace_project_issues_path(project.namespace, project) find('#check_all_issues').click - find('.js-update-assignee').click + click_update_assignee_button find('.dropdown-menu-user-link', text: user.username).click click_update_issues_button @@ -57,14 +57,11 @@ feature 'Multiple issue updating from issues#index', feature: true do visit namespace_project_issues_path(project.namespace, project) find('#check_all_issues').click - find('.js-update-assignee').click + click_update_assignee_button click_link 'Unassigned' click_update_issues_button - - within first('.issue .controls') do - expect(page).to have_no_selector('.author_link') - end + expect(find('.issue:first-child .controls')).not_to have_css('.author_link') end end @@ -95,7 +92,7 @@ feature 'Multiple issue updating from issues#index', feature: true do find('.dropdown-menu-milestone a', text: "No Milestone").click click_update_issues_button - expect(first('.issue')).not_to have_content milestone.title + expect(find('.issue:first-child')).not_to have_content milestone.title end end @@ -111,7 +108,13 @@ feature 'Multiple issue updating from issues#index', feature: true do create(:issue, project: project, milestone: milestone) end + def click_update_assignee_button + find('.js-update-assignee').click + wait_for_ajax + end + def click_update_issues_button find('.update_selected_issues').click + wait_for_ajax end end diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 9271964166a..f6fb6a72d22 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -75,12 +75,13 @@ describe 'Issues', feature: true do fill_in 'issue_title', with: 'bug 345' fill_in 'issue_description', with: 'bug description' + find('#issuable-due-date').click - page.within '.datepicker' do + page.within '.ui-datepicker' do click_link date.day end - expect(find('#issuable-due-date', visible: false).value).to eq date.to_s + expect(find('#issuable-due-date').value).to eq date.to_s click_button 'Submit issue' @@ -100,18 +101,19 @@ describe 'Issues', feature: true do it 'should save with due date' do date = Date.today.at_beginning_of_month - expect(find('#issuable-due-date', visible: false).value).to eq date.to_s + expect(find('#issuable-due-date').value).to eq date.to_s date = date.tomorrow fill_in 'issue_title', with: 'bug 345' fill_in 'issue_description', with: 'bug description' + find('#issuable-due-date').click - page.within '.datepicker' do + page.within '.ui-datepicker' do click_link date.day end - expect(find('#issuable-due-date', visible: false).value).to eq date.to_s + expect(find('#issuable-due-date').value).to eq date.to_s click_button 'Save changes' @@ -125,7 +127,7 @@ describe 'Issues', feature: true do describe 'Issue info' do it 'excludes award_emoji from comment count' do issue = create(:issue, author: @user, assignee: @user, project: project, title: 'foobar') - create(:upvote_note, noteable: issue, project: project) + create(:award_emoji, awardable: issue) visit namespace_project_issues_path(project.namespace, project, assignee_id: @user.id) @@ -365,13 +367,9 @@ describe 'Issues', feature: true do page.within('.assignee') do expect(page).to have_content "#{@user.name}" - end - find('.block.assignee .edit-link').click - sleep 2 # wait for ajax stuff to complete - first('.dropdown-menu-user-link').click - sleep 2 - page.within('.assignee') do + click_link 'Edit' + click_link 'Unassigned' expect(page).to have_content 'No assignee' end diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb index c1b178c3b6c..72b5ff231f7 100644 --- a/spec/features/login_spec.rb +++ b/spec/features/login_spec.rb @@ -33,11 +33,11 @@ feature 'Login', feature: true do before do login_with(user, remember: true) - expect(page).to have_content('Two-factor Authentication') + expect(page).to have_content('Two-Factor Authentication') end def enter_code(code) - fill_in 'Two-factor Authentication code', with: code + fill_in 'Two-Factor Authentication code', with: code click_button 'Verify code' end @@ -143,12 +143,12 @@ feature 'Login', feature: true do context 'within the grace period' do it 'redirects to two-factor configuration page' do - expect(current_path).to eq new_profile_two_factor_auth_path - expect(page).to have_content('You must enable Two-factor Authentication for your account before') + expect(current_path).to eq profile_two_factor_auth_path + expect(page).to have_content('You must enable Two-Factor Authentication for your account before') end - it 'disallows skipping two-factor configuration' do - expect(current_path).to eq new_profile_two_factor_auth_path + it 'allows skipping two-factor configuration', js: true do + expect(current_path).to eq profile_two_factor_auth_path click_link 'Configure it later' expect(current_path).to eq root_path @@ -159,26 +159,26 @@ feature 'Login', feature: true do let(:user) { create(:user, otp_grace_period_started_at: 9999.hours.ago) } it 'redirects to two-factor configuration page' do - expect(current_path).to eq new_profile_two_factor_auth_path - expect(page).to have_content('You must enable Two-factor Authentication for your account.') + expect(current_path).to eq profile_two_factor_auth_path + expect(page).to have_content('You must enable Two-Factor Authentication for your account.') end - it 'disallows skipping two-factor configuration' do - expect(current_path).to eq new_profile_two_factor_auth_path + it 'disallows skipping two-factor configuration', js: true do + expect(current_path).to eq profile_two_factor_auth_path expect(page).not_to have_link('Configure it later') end end end - context 'without grace pariod defined' do + context 'without grace period defined' do before(:each) do stub_application_setting(two_factor_grace_period: 0) login_with(user) end it 'redirects to two-factor configuration page' do - expect(current_path).to eq new_profile_two_factor_auth_path - expect(page).to have_content('You must enable Two-factor Authentication for your account.') + expect(current_path).to eq profile_two_factor_auth_path + expect(page).to have_content('You must enable Two-Factor Authentication for your account.') end end end diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb index 1d892fe1a55..09ccc77c101 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown_spec.rb @@ -165,22 +165,32 @@ describe 'GitLab Markdown', feature: true do describe 'ExternalLinkFilter' do it 'adds nofollow to external link' do link = doc.at_css('a:contains("Google")') + expect(link.attr('rel')).to include('nofollow') end it 'adds noreferrer to external link' do link = doc.at_css('a:contains("Google")') + expect(link.attr('rel')).to include('noreferrer') end + it 'adds _blank to target attribute for external links' do + link = doc.at_css('a:contains("Google")') + + expect(link.attr('target')).to match('_blank') + end + it 'ignores internal link' do link = doc.at_css('a:contains("GitLab Root")') + expect(link.attr('rel')).not_to match 'nofollow' + expect(link.attr('target')).not_to match '_blank' end end end - before(:all) do + before do @feat = MarkdownFeature.new # `markdown` helper expects a `@project` variable @@ -188,7 +198,7 @@ describe 'GitLab Markdown', feature: true do end context 'default pipeline' do - before(:all) do + before do @html = markdown(@feat.raw_markdown) end @@ -231,13 +241,14 @@ describe 'GitLab Markdown', feature: true do context 'wiki pipeline' do before do @project_wiki = @feat.project_wiki + @project_wiki_page = @feat.project_wiki_page file = Gollum::File.new(@project_wiki.wiki) expect(file).to receive(:path).and_return('images/example.jpg') expect(@project_wiki).to receive(:find_file).with('images/example.jpg').and_return(file) allow(@project_wiki).to receive(:wiki_base_path) { '/namespace1/gitlabhq/wikis' } - @html = markdown(@feat.raw_markdown, { pipeline: :wiki, project_wiki: @project_wiki }) + @html = markdown(@feat.raw_markdown, { pipeline: :wiki, project_wiki: @project_wiki, page_slug: @project_wiki_page.slug }) end it_behaves_like 'all pipelines' diff --git a/spec/features/merge_requests/award_spec.rb b/spec/features/merge_requests/award_spec.rb new file mode 100644 index 00000000000..007f67d6080 --- /dev/null +++ b/spec/features/merge_requests/award_spec.rb @@ -0,0 +1,49 @@ +require 'rails_helper' + +feature 'Merge request awards', js: true, feature: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:merge_request) { create(:merge_request, source_project: project) } + + describe 'logged in' do + before do + login_as(user) + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'should add award to merge request' do + first('.js-emoji-btn').click + expect(page).to have_selector('.js-emoji-btn.active') + expect(first('.js-emoji-btn')).to have_content '1' + + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + expect(first('.js-emoji-btn')).to have_content '1' + end + + it 'should remove award from merge request' do + first('.js-emoji-btn').click + find('.js-emoji-btn.active').click + expect(first('.js-emoji-btn')).to have_content '0' + + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + expect(first('.js-emoji-btn')).to have_content '0' + end + + it 'should only have one menu on the page' do + first('.js-add-award').click + expect(page).to have_selector('.emoji-menu') + + expect(page).to have_selector('.emoji-menu', count: 1) + end + end + + describe 'logged out' do + before do + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'should not see award menu button' do + expect(page).not_to have_selector('.js-award-holder') + end + end +end diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb index edc0bdec3db..b4d2201c729 100644 --- a/spec/features/merge_requests/created_from_fork_spec.rb +++ b/spec/features/merge_requests/created_from_fork_spec.rb @@ -29,9 +29,9 @@ feature 'Merge request created from fork' do include WaitForAjax given(:pipeline) do - create(:ci_commit_with_two_jobs, project: fork_project, - sha: merge_request.last_commit.id, - ref: merge_request.source_branch) + create(:ci_pipeline_with_two_job, project: fork_project, + sha: merge_request.last_commit.id, + ref: merge_request.source_branch) end background { pipeline.create_builds(user) } diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index 7aa7eb965e9..c5e6412d7bf 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -12,8 +12,8 @@ feature 'Merge When Build Succeeds', feature: true, js: true do end context "Active build for Merge Request" do - let!(:ci_commit) { create(:ci_commit, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) } - let!(:ci_build) { create(:ci_build, commit: ci_commit) } + let!(:pipeline) { create(:ci_pipeline, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) } + let!(:ci_build) { create(:ci_build, pipeline: pipeline) } before do login_as user @@ -47,8 +47,8 @@ feature 'Merge When Build Succeeds', feature: true, js: true do merge_user: user, title: "MepMep", merge_when_build_succeeds: true) end - let!(:ci_commit) { create(:ci_commit, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) } - let!(:ci_build) { create(:ci_build, commit: ci_commit) } + let!(:pipeline) { create(:ci_pipeline, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) } + let!(:ci_build) { create(:ci_build, pipeline: pipeline) } before do login_as user diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds.rb new file mode 100644 index 00000000000..65e9185ec24 --- /dev/null +++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds.rb @@ -0,0 +1,105 @@ +require 'spec_helper' + +feature 'Only allow merge requests to be merged if the build succeeds', feature: true do + let(:project) { create(:project, :public) } + let(:merge_request) { create(:merge_request_with_diffs, source_project: project) } + + before do + login_as merge_request.author + + project.team << [merge_request.author, :master] + end + + context 'project does not have CI enabled' do + it 'allows MR to be merged' do + visit_merge_request(merge_request) + + expect(page).to have_button 'Accept Merge Request' + end + end + + context 'when project has CI enabled' do + let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) } + + context 'when merge requests can only be merged if the build succeeds' do + before do + project.update_attribute(:only_allow_merge_if_build_succeeds, true) + end + + context 'when CI is running' do + before { pipeline.update_column(:status, :running) } + + it 'does not allow to merge immediately' do + visit_merge_request(merge_request) + + expect(page).to have_button 'Merge When Build Succeeds' + expect(page).not_to have_button 'Select Merge Moment' + end + end + + context 'when CI failed' do + before { pipeline.update_column(:status, :failed) } + + it 'does not allow MR to be merged' do + visit_merge_request(merge_request) + + expect(page).not_to have_button 'Accept Merge Request' + expect(page).to have_content('Please retry the build or push a new commit to fix the failure.') + end + end + + context 'when CI succeeded' do + before { pipeline.update_column(:status, :success) } + + it 'allows MR to be merged' do + visit_merge_request(merge_request) + + expect(page).to have_button 'Accept Merge Request' + end + end + end + + context 'when merge requests can be merged when the build failed' do + before do + project.update_attribute(:only_allow_merge_if_build_succeeds, false) + end + + context 'when CI is running' do + before { pipeline.update_column(:status, :running) } + + it 'allows MR to be merged immediately', js: true do + visit_merge_request(merge_request) + + expect(page).to have_button 'Merge When Build Succeeds' + + click_button 'Select Merge Moment' + expect(page).to have_content 'Merge Immediately' + end + end + + context 'when CI failed' do + before { pipeline.update_column(:status, :failed) } + + it 'allows MR to be merged' do + visit_merge_request(merge_request) + + expect(page).to have_button 'Accept Merge Request' + end + end + + context 'when CI succeeded' do + before { pipeline.update_column(:status, :success) } + + it 'allows MR to be merged' do + visit_merge_request(merge_request) + + expect(page).to have_button 'Accept Merge Request' + end + end + end + end + + def visit_merge_request(merge_request) + visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) + end +end diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index 2835cf44494..737efcef45d 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -4,20 +4,6 @@ describe 'Comments', feature: true do include RepoHelpers include WaitForAjax - describe 'On merge requests page', feature: true do - it 'excludes award_emoji from comment count' do - merge_request = create(:merge_request) - project = merge_request.source_project - create(:upvote_note, noteable: merge_request, project: project) - - login_as :admin - visit namespace_project_merge_requests_path(project.namespace, project) - - expect(merge_request.mr_and_commit_notes.count).to eq 1 - expect(page.all('.merge-request-no-comments').first.text).to eq "0" - end - end - describe 'On a merge request', js: true, feature: true do let!(:project) { create(:project) } let!(:merge_request) do @@ -147,17 +133,6 @@ describe 'Comments', feature: true do end end end - - describe 'comment info' do - it 'excludes award_emoji from comment count' do - create(:upvote_note, noteable: merge_request, project: project) - - visit namespace_project_merge_request_path(project.namespace, project, merge_request) - - expect(merge_request.mr_and_commit_notes.count).to eq 2 - expect(find('.notes-tab span.badge').text).to eq "1" - end - end end describe 'On a merge request diff', js: true, feature: true do diff --git a/spec/features/pipelines_spec.rb b/spec/features/pipelines_spec.rb index acd6fb3538c..98703ef3ac4 100644 --- a/spec/features/pipelines_spec.rb +++ b/spec/features/pipelines_spec.rb @@ -12,7 +12,7 @@ describe "Pipelines" do end describe 'GET /:project/pipelines' do - let!(:pipeline) { create(:ci_commit, project: project, ref: 'master', status: 'running') } + let!(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', status: 'running') } [:all, :running, :branches].each do |scope| context "displaying #{scope}" do @@ -31,7 +31,7 @@ describe "Pipelines" do end context 'cancelable pipeline' do - let!(:running) { create(:ci_build, :running, commit: pipeline, stage: 'test', commands: 'test') } + let!(:running) { create(:ci_build, :running, pipeline: pipeline, stage: 'test', commands: 'test') } before { visit namespace_project_pipelines_path(project.namespace, project) } @@ -47,7 +47,7 @@ describe "Pipelines" do end context 'retryable pipelines' do - let!(:failed) { create(:ci_build, :failed, commit: pipeline, stage: 'test', commands: 'test') } + let!(:failed) { create(:ci_build, :failed, pipeline: pipeline, stage: 'test', commands: 'test') } before { visit namespace_project_pipelines_path(project.namespace, project) } @@ -64,7 +64,7 @@ describe "Pipelines" do context 'for generic statuses' do context 'when running' do - let!(:running) { create(:generic_commit_status, status: 'running', commit: pipeline, stage: 'test') } + let!(:running) { create(:generic_commit_status, status: 'running', pipeline: pipeline, stage: 'test') } before { visit namespace_project_pipelines_path(project.namespace, project) } @@ -78,7 +78,7 @@ describe "Pipelines" do end context 'when failed' do - let!(:running) { create(:generic_commit_status, status: 'failed', commit: pipeline, stage: 'test') } + let!(:running) { create(:generic_commit_status, status: 'failed', pipeline: pipeline, stage: 'test') } before { visit namespace_project_pipelines_path(project.namespace, project) } @@ -94,7 +94,7 @@ describe "Pipelines" do context 'downloadable pipelines' do context 'with artifacts' do - let!(:with_artifacts) { create(:ci_build, :artifacts, :success, commit: pipeline, name: 'rspec tests', stage: 'test') } + let!(:with_artifacts) { create(:ci_build, :artifacts, :success, pipeline: pipeline, name: 'rspec tests', stage: 'test') } before { visit namespace_project_pipelines_path(project.namespace, project) } @@ -103,7 +103,7 @@ describe "Pipelines" do end context 'without artifacts' do - let!(:without_artifacts) { create(:ci_build, :success, commit: pipeline, name: 'rspec', stage: 'test') } + let!(:without_artifacts) { create(:ci_build, :success, pipeline: pipeline, name: 'rspec', stage: 'test') } it { expect(page).not_to have_selector('.build-artifacts') } end @@ -111,13 +111,13 @@ describe "Pipelines" do end describe 'GET /:project/pipelines/:id' do - let(:pipeline) { create(:ci_commit, project: project, ref: 'master') } + let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master') } before do - @success = create(:ci_build, :success, commit: pipeline, stage: 'build', name: 'build') - @failed = create(:ci_build, :failed, commit: pipeline, stage: 'test', name: 'test', commands: 'test') - @running = create(:ci_build, :running, commit: pipeline, stage: 'deploy', name: 'deploy') - @external = create(:generic_commit_status, status: 'success', commit: pipeline, name: 'jenkins', stage: 'external') + @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build') + @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test') + @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy') + @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external') end before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) } @@ -165,9 +165,9 @@ describe "Pipelines" do before { fill_in('Create for', with: 'master') } context 'with gitlab-ci.yml' do - before { stub_ci_commit_to_return_yaml_file } + before { stub_ci_pipeline_to_return_yaml_file } - it { expect{ click_on 'Create pipeline' }.to change{ Ci::Commit.count }.by(1) } + it { expect{ click_on 'Create pipeline' }.to change{ Ci::Pipeline.count }.by(1) } end context 'without gitlab-ci.yml' do diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb index 8f645438cff..787bf42d048 100644 --- a/spec/features/profiles/preferences_spec.rb +++ b/spec/features/profiles/preferences_spec.rb @@ -54,7 +54,7 @@ describe 'Profile > Preferences', feature: true do end end - describe 'User changes their default dashboard' do + describe 'User changes their default dashboard', js: true do it 'creates a flash message' do select 'Starred Projects', from: 'user_dashboard' click_button 'Save' @@ -66,8 +66,10 @@ describe 'Profile > Preferences', feature: true do select 'Starred Projects', from: 'user_dashboard' click_button 'Save' - click_link 'Dashboard' - expect(page.current_path).to eq starred_dashboard_projects_path + allowing_for_delay do + find('#logo').click + expect(page.current_path).to eq starred_dashboard_projects_path + end click_link 'Your Projects' expect(page.current_path).to eq dashboard_projects_path diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb index 40ba0bdc115..15c381c0f5a 100644 --- a/spec/features/projects/commit/builds_spec.rb +++ b/spec/features/projects/commit/builds_spec.rb @@ -11,9 +11,9 @@ feature 'project commit builds' do context 'when no builds triggered yet' do background do - create(:ci_commit, project: project, - sha: project.commit.sha, - ref: 'master') + create(:ci_pipeline, project: project, + sha: project.commit.sha, + ref: 'master') end scenario 'user views commit builds page' do diff --git a/spec/features/projects/commits/cherry_pick_spec.rb b/spec/features/projects/commits/cherry_pick_spec.rb index 0559b02f321..f88c0616b52 100644 --- a/spec/features/projects/commits/cherry_pick_spec.rb +++ b/spec/features/projects/commits/cherry_pick_spec.rb @@ -16,6 +16,7 @@ describe 'Cherry-pick Commits' do it do visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id) 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' diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb new file mode 100644 index 00000000000..461f1737928 --- /dev/null +++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +feature 'Issue prioritization', feature: true do + + let(:user) { create(:user) } + let(:project) { create(:project, name: 'test', namespace: user.namespace) } + + # Labels + let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) } + let(:label_2) { create(:label, title: 'label_2', project: project, priority: 2) } + let(:label_3) { create(:label, title: 'label_3', project: project, priority: 3) } + let(:label_4) { create(:label, title: 'label_4', project: project, priority: 4) } + let(:label_5) { create(:label, title: 'label_5', project: project) } # no priority + + # According to https://gitlab.com/gitlab-org/gitlab-ce/issues/14189#note_4360653 + context 'when issues have one label' do + scenario 'Are sorted properly' do + + # Issues + issue_1 = create(:issue, title: 'issue_1', project: project) + issue_2 = create(:issue, title: 'issue_2', project: project) + issue_3 = create(:issue, title: 'issue_3', project: project) + issue_4 = create(:issue, title: 'issue_4', project: project) + issue_5 = create(:issue, title: 'issue_5', project: project) + + # Assign labels to issues disorderly + issue_4.labels << label_1 + issue_3.labels << label_2 + issue_5.labels << label_3 + issue_2.labels << label_4 + issue_1.labels << label_5 + + login_as user + visit namespace_project_issues_path(project.namespace, project, sort: 'priority') + + # Ensure we are indicating that issues are sorted by priority + expect(page).to have_selector('.dropdown-toggle', text: 'Priority') + + page.within('.issues-holder') do + issue_titles = all('.issues-list .issue-title-text').map(&:text) + + expect(issue_titles).to eq(['issue_4', 'issue_3', 'issue_5', 'issue_2', 'issue_1']) + end + end + end + + context 'when issues have multiple labels' do + scenario 'Are sorted properly' do + + # Issues + issue_1 = create(:issue, title: 'issue_1', project: project) + issue_2 = create(:issue, title: 'issue_2', project: project) + issue_3 = create(:issue, title: 'issue_3', project: project) + issue_4 = create(:issue, title: 'issue_4', project: project) + issue_5 = create(:issue, title: 'issue_5', project: project) + issue_6 = create(:issue, title: 'issue_6', project: project) + issue_7 = create(:issue, title: 'issue_7', project: project) + issue_8 = create(:issue, title: 'issue_8', project: project) + + # Assign labels to issues disorderly + issue_5.labels << label_1 # 1 + issue_5.labels << label_2 + issue_8.labels << label_1 # 2 + issue_1.labels << label_2 # 3 + issue_1.labels << label_3 + issue_3.labels << label_2 # 4 + issue_3.labels << label_4 + issue_7.labels << label_2 # 5 + issue_2.labels << label_3 # 6 + issue_4.labels << label_4 # 7 + issue_6.labels << label_5 # 8 - No priority + + login_as user + visit namespace_project_issues_path(project.namespace, project, sort: 'priority') + + expect(page).to have_selector('.dropdown-toggle', text: 'Priority') + + page.within('.issues-holder') do + issue_titles = all('.issues-list .issue-title-text').map(&:text) + + expect(issue_titles[0..1]).to contain_exactly('issue_5', 'issue_8') + expect(issue_titles[2..4]).to contain_exactly('issue_1', 'issue_3', 'issue_7') + expect(issue_titles[5..-1]).to eq(['issue_2', 'issue_4', 'issue_6']) + end + end + end +end diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb new file mode 100644 index 00000000000..8550d279d09 --- /dev/null +++ b/spec/features/projects/labels/update_prioritization_spec.rb @@ -0,0 +1,115 @@ +require 'spec_helper' + +feature 'Prioritize labels', feature: true do + include WaitForAjax + + context 'when project belongs to user' do + let(:user) { create(:user) } + let(:project) { create(:project, name: 'test', namespace: user.namespace) } + + scenario 'user can prioritize a label', js: true do + bug = create(:label, title: 'bug') + wontfix = create(:label, title: 'wontfix') + + project.labels << bug + project.labels << wontfix + + login_as user + visit namespace_project_labels_path(project.namespace, project) + + expect(page).to have_content('No prioritized labels yet') + + page.within('.other-labels') do + first('.js-toggle-priority').click + wait_for_ajax + expect(page).not_to have_content('bug') + end + + page.within('.prioritized-labels') do + expect(page).not_to have_content('No prioritized labels yet') + expect(page).to have_content('bug') + end + end + + scenario 'user can unprioritize a label', js: true do + bug = create(:label, title: 'bug', priority: 1) + wontfix = create(:label, title: 'wontfix') + + project.labels << bug + project.labels << wontfix + + login_as user + visit namespace_project_labels_path(project.namespace, project) + + expect(page).to have_content('bug') + + page.within('.prioritized-labels') do + first('.js-toggle-priority').click + wait_for_ajax + expect(page).not_to have_content('bug') + end + + page.within('.other-labels') do + expect(page).to have_content('bug') + expect(page).to have_content('wontfix') + end + end + + scenario 'user can sort prioritized labels and persist across reloads', js: true do + bug = create(:label, title: 'bug', priority: 1) + wontfix = create(:label, title: 'wontfix', priority: 2) + + project.labels << bug + project.labels << wontfix + + login_as user + visit namespace_project_labels_path(project.namespace, project) + + expect(page).to have_content 'bug' + expect(page).to have_content 'wontfix' + + # Sort labels + find("#label_#{bug.id}").drag_to find("#label_#{wontfix.id}") + + page.within('.prioritized-labels') do + expect(first('li')).to have_content('wontfix') + expect(page.all('li').last).to have_content('bug') + end + + visit current_url + + page.within('.prioritized-labels') do + expect(first('li')).to have_content('wontfix') + expect(page.all('li').last).to have_content('bug') + end + end + end + + context 'as a guest' do + it 'can not prioritize labels' do + user = create(:user) + guest = create(:user) + project = create(:project, name: 'test', namespace: user.namespace) + + create(:label, title: 'bug') + + login_as guest + visit namespace_project_labels_path(project.namespace, project) + + expect(page).not_to have_css('.prioritized-labels') + end + end + + context 'as a non signed in user' do + it 'can not prioritize labels' do + user = create(:user) + project = create(:project, name: 'test', namespace: user.namespace) + + create(:label, title: 'bug') + + visit namespace_project_labels_path(project.namespace, project) + + expect(page).not_to have_css('.prioritized-labels') + end + end +end diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index 4def4f99bc0..c5f741709ad 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -142,8 +142,8 @@ describe "Public Project Access", feature: true do end describe "GET /:project_path/builds/:id" do - let(:commit) { create(:ci_commit, project: project) } - let(:build) { create(:ci_build, commit: commit) } + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:build) { create(:ci_build, pipeline: pipeline) } subject { namespace_project_build_path(project.namespace, project, build.id) } context "when allowed for public" do diff --git a/spec/features/todos/target_state_spec.rb b/spec/features/todos/target_state_spec.rb index 72491ac7e61..32fa88a2b21 100644 --- a/spec/features/todos/target_state_spec.rb +++ b/spec/features/todos/target_state_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' feature 'Todo target states', feature: true do let(:user) { create(:user) } let(:author) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } before do login_as user diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb index 4e627753cc7..8e1833a069e 100644 --- a/spec/features/todos/todos_spec.rb +++ b/spec/features/todos/todos_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Dashboard Todos', feature: true do let(:user) { create(:user) } let(:author) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } let(:issue) { create(:issue) } describe 'GET /dashboard/todos' do @@ -49,7 +49,7 @@ describe 'Dashboard Todos', feature: true do note1 = create(:note_on_issue, note: "Hello #{label1.to_reference(format: :name)}", noteable_id: issue.id, noteable_type: 'Issue', project: issue.project) create(:todo, :mentioned, project: project, target: issue, user: user, note_id: note1.id) - project2 = create(:project) + project2 = create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) label2 = create(:label, project: project2) issue2 = create(:issue, project: project2) note2 = create(:note_on_issue, note: "Test #{label2.to_reference(format: :name)}", noteable_id: issue2.id, noteable_type: 'Issue', project: project2) @@ -98,5 +98,18 @@ describe 'Dashboard Todos', feature: true do end end end + + context 'User has a Todo in a project pending deletion' do + before do + deleted_project = create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC, pending_delete: true) + create(:todo, :mentioned, user: user, project: deleted_project, target: issue, author: author) + login_as(user) + visit dashboard_todos_path + end + + it 'shows "All done" message' do + expect(page).to have_content "You're all done!" + end + end end end diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb new file mode 100644 index 00000000000..366a90228b1 --- /dev/null +++ b/spec/features/u2f_spec.rb @@ -0,0 +1,239 @@ +require 'spec_helper' + +feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: true, js: true do + def register_u2f_device(u2f_device = nil) + u2f_device ||= FakeU2fDevice.new(page) + u2f_device.respond_to_u2f_registration + click_on 'Setup New U2F Device' + expect(page).to have_content('Your device was successfully set up') + click_on 'Register U2F Device' + u2f_device + end + + describe "registration" do + let(:user) { create(:user) } + before { login_as(user) } + + describe 'when 2FA via OTP is disabled' do + it 'allows registering a new device' do + visit profile_account_path + click_on 'Enable Two-Factor Authentication' + + register_u2f_device + + expect(page.body).to match('Your U2F device was registered') + end + + it 'allows registering more than one device' do + visit profile_account_path + + # First device + click_on 'Enable Two-Factor Authentication' + register_u2f_device + expect(page.body).to match('Your U2F device was registered') + + # Second device + click_on 'Manage Two-Factor Authentication' + register_u2f_device + expect(page.body).to match('Your U2F device was registered') + click_on 'Manage Two-Factor Authentication' + + expect(page.body).to match('You have 2 U2F devices registered') + end + end + + describe 'when 2FA via OTP is enabled' do + before { user.update_attributes(otp_required_for_login: true) } + + it 'allows registering a new device' do + visit profile_account_path + click_on 'Manage Two-Factor Authentication' + expect(page.body).to match("You've already enabled two-factor authentication using mobile") + + register_u2f_device + + expect(page.body).to match('Your U2F device was registered') + end + + it 'allows registering more than one device' do + visit profile_account_path + + # First device + click_on 'Manage Two-Factor Authentication' + register_u2f_device + expect(page.body).to match('Your U2F device was registered') + + # Second device + click_on 'Manage Two-Factor Authentication' + register_u2f_device + expect(page.body).to match('Your U2F device was registered') + + click_on 'Manage Two-Factor Authentication' + expect(page.body).to match('You have 2 U2F devices registered') + end + end + + it 'allows the same device to be registered for multiple users' do + # First user + visit profile_account_path + click_on 'Enable Two-Factor Authentication' + u2f_device = register_u2f_device + expect(page.body).to match('Your U2F device was registered') + logout + + # Second user + login_as(:user) + visit profile_account_path + click_on 'Enable Two-Factor Authentication' + register_u2f_device(u2f_device) + expect(page.body).to match('Your U2F device was registered') + + expect(U2fRegistration.count).to eq(2) + end + + context "when there are form errors" do + it "doesn't register the device if there are errors" do + visit profile_account_path + click_on 'Enable Two-Factor Authentication' + + # Have the "u2f device" respond with bad data + page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };") + click_on 'Setup New U2F Device' + expect(page).to have_content('Your device was successfully set up') + click_on 'Register U2F Device' + + expect(U2fRegistration.count).to eq(0) + expect(page.body).to match("The form contains the following error") + expect(page.body).to match("did not send a valid JSON response") + end + + it "allows retrying registration" do + visit profile_account_path + click_on 'Enable Two-Factor Authentication' + + # Failed registration + page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };") + click_on 'Setup New U2F Device' + expect(page).to have_content('Your device was successfully set up') + click_on 'Register U2F Device' + expect(page.body).to match("The form contains the following error") + + # Successful registration + register_u2f_device + + expect(page.body).to match('Your U2F device was registered') + expect(U2fRegistration.count).to eq(1) + end + end + end + + describe "authentication" do + let(:user) { create(:user) } + + before do + # Register and logout + login_as(user) + visit profile_account_path + click_on 'Enable Two-Factor Authentication' + @u2f_device = register_u2f_device + logout + end + + describe "when 2FA via OTP is disabled" do + it "allows logging in with the U2F device" do + login_with(user) + + @u2f_device.respond_to_u2f_authentication + click_on "Login Via U2F Device" + expect(page.body).to match('We heard back from your U2F device') + click_on "Authenticate via U2F Device" + + expect(page.body).to match('Signed in successfully') + end + end + + describe "when 2FA via OTP is enabled" do + it "allows logging in with the U2F device" do + user.update_attributes(otp_required_for_login: true) + login_with(user) + + @u2f_device.respond_to_u2f_authentication + click_on "Login Via U2F Device" + expect(page.body).to match('We heard back from your U2F device') + click_on "Authenticate via U2F Device" + + expect(page.body).to match('Signed in successfully') + end + end + + describe "when a given U2F device has already been registered by another user" do + describe "but not the current user" do + it "does not allow logging in with that particular device" do + # Register current user with the different U2F device + current_user = login_as(:user) + visit profile_account_path + click_on 'Enable Two-Factor Authentication' + register_u2f_device + logout + + # Try authenticating user with the old U2F device + login_as(current_user) + @u2f_device.respond_to_u2f_authentication + click_on "Login Via U2F Device" + expect(page.body).to match('We heard back from your U2F device') + click_on "Authenticate via U2F Device" + + expect(page.body).to match('Authentication via U2F device failed') + end + end + + describe "and also the current user" do + it "allows logging in with that particular device" do + # Register current user with the same U2F device + current_user = login_as(:user) + visit profile_account_path + click_on 'Enable Two-Factor Authentication' + register_u2f_device(@u2f_device) + logout + + # Try authenticating user with the same U2F device + login_as(current_user) + @u2f_device.respond_to_u2f_authentication + click_on "Login Via U2F Device" + expect(page.body).to match('We heard back from your U2F device') + click_on "Authenticate via U2F Device" + + expect(page.body).to match('Signed in successfully') + end + end + end + + describe "when a given U2F device has not been registered" do + it "does not allow logging in with that particular device" do + unregistered_device = FakeU2fDevice.new(page) + login_as(user) + unregistered_device.respond_to_u2f_authentication + click_on "Login Via U2F Device" + expect(page.body).to match('We heard back from your U2F device') + click_on "Authenticate via U2F Device" + + expect(page.body).to match('Authentication via U2F device failed') + end + end + end + + describe "when two-factor authentication is disabled" do + let(:user) { create(:user) } + + before do + login_as(user) + visit profile_account_path + click_on 'Enable Two-Factor Authentication' + register_u2f_device + end + + it "deletes u2f registrations" do + expect { click_on "Disable" }.to change { U2fRegistration.count }.from(1).to(0) + end + end +end |