diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-16 15:08:32 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-16 15:08:32 +0300 |
commit | c158fa8d69c704663d289341a014c44c062cda88 (patch) | |
tree | d0cac82a9ac9e9ad28bb0030266eb8d5dc91fbbc /spec | |
parent | b806264d29b8d52ccb78a41dcc3d67f2b040700c (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
31 files changed, 1150 insertions, 952 deletions
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index 4f4f9e5143b..8fb9f0c516c 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -148,6 +148,19 @@ describe Groups::MilestonesController do expect(response).to have_gitlab_http_status(200) expect(response.content_type).to eq 'application/json' end + + context 'for a subgroup' do + let(:subgroup) { create(:group, parent: group) } + + it 'includes ancestor group milestones' do + get :index, params: { group_id: subgroup.to_param }, format: :json + + milestones = json_response + + expect(milestones.count).to eq(1) + expect(milestones.first['title']).to eq('group milestone') + end + end end context 'external authorization' do diff --git a/spec/features/issues/user_creates_issue_by_email_spec.rb b/spec/features/issues/user_creates_issue_by_email_spec.rb new file mode 100644 index 00000000000..c73a65849cc --- /dev/null +++ b/spec/features/issues/user_creates_issue_by_email_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Issues > User creates issue by email' do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public) } + + before do + sign_in(user) + + project.add_developer(user) + end + + describe 'new issue by email' do + shared_examples 'show the email in the modal' do + let(:issue) { create(:issue, project: project) } + + before do + project.issues << issue + stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab") + + visit project_issues_path(project) + click_button('Email a new issue') + end + + it 'click the button to show modal for the new email' do + page.within '#issuable-email-modal' do + email = project.new_issuable_address(user, 'issue') + + expect(page).to have_selector("input[value='#{email}']") + end + end + end + + context 'with existing issues' do + let!(:issue) { create(:issue, project: project, author: user) } + + it_behaves_like 'show the email in the modal' + end + + context 'without existing issues' do + it_behaves_like 'show the email in the modal' + end + end +end diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb index 39ce3415727..b0a2a734877 100644 --- a/spec/features/issues/user_creates_issue_spec.rb +++ b/spec/features/issues/user_creates_issue_spec.rb @@ -3,8 +3,32 @@ require "spec_helper" describe "User creates issue" do - let(:project) { create(:project_empty_repo, :public) } - let(:user) { create(:user) } + include DropzoneHelper + + let_it_be(:project) { create(:project_empty_repo, :public) } + let_it_be(:user) { create(:user) } + + context "when unauthenticated" do + before do + sign_out(:user) + end + + it "redirects to signin then back to new issue after signin" do + create(:issue, project: project) + + visit project_issues_path(project) + + page.within ".nav-controls" do + click_link "New issue" + end + + expect(current_path).to eq new_user_session_path + + gitlab_sign_in(create(:user)) + + expect(current_path).to eq new_project_issue_path(project) + end + end context "when signed in as guest" do before do @@ -92,6 +116,104 @@ describe "User creates issue" do .and have_content(label_titles.first) end end + + context 'with due date', :js do + it 'saves with due date' do + date = Date.today.at_beginning_of_month + + fill_in 'issue_title', with: 'bug 345' + fill_in 'issue_description', with: 'bug description' + find('#issuable-due-date').click + + page.within '.pika-single' do + click_button date.day + end + + expect(find('#issuable-due-date').value).to eq date.to_s + + click_button 'Submit issue' + + page.within '.issuable-sidebar' do + expect(page).to have_content date.to_s(:medium) + end + end + end + + context 'dropzone upload file', :js do + before do + visit new_project_issue_path(project) + end + + it 'uploads file when dragging into textarea' do + dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') + + expect(page.find_field("issue_description").value).to have_content 'banana_sample' + end + + it "doesn't add double newline to end of a single attachment markdown" do + dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') + + expect(page.find_field("issue_description").value).not_to match /\n\n$/ + end + + it "cancels a file upload correctly" do + slow_requests do + dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false) + + click_button 'Cancel' + end + + expect(page).to have_button('Attach a file') + expect(page).not_to have_button('Cancel') + expect(page).not_to have_selector('.uploading-progress-container', visible: true) + end + end + + context 'form filled by URL parameters' do + let(:project) { create(:project, :public, :repository) } + + before do + project.repository.create_file( + user, + '.gitlab/issue_templates/bug.md', + 'this is a test "bug" template', + message: 'added issue template', + branch_name: 'master') + + visit new_project_issue_path(project, issuable_template: 'bug') + end + + it 'fills in template' do + expect(find('.js-issuable-selector .dropdown-toggle-text')).to have_content('bug') + end + end + + context 'suggestions', :js do + it 'displays list of related issues' do + issue = create(:issue, project: project) + create(:issue, project: project, title: 'test issue') + + visit new_project_issue_path(project) + + fill_in 'issue_title', with: issue.title + + expect(page).to have_selector('.suggestion-item', count: 1) + end + end + + it 'clears local storage after creating a new issue', :js do + 2.times do + visit new_project_issue_path(project) + wait_for_requests + + expect(page).to have_field('Title', with: '') + + fill_in 'issue_title', with: 'bug 345' + fill_in 'issue_description', with: 'bug description' + + click_button 'Submit issue' + end + end end context "when signed in as user with special characters in their name" do diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb index 0afc19d9519..ad984cf07e2 100644 --- a/spec/features/issues/user_edits_issue_spec.rb +++ b/spec/features/issues/user_edits_issue_spec.rb @@ -2,26 +2,283 @@ require "spec_helper" -describe "User edits issue", :js do - set(:project) { create(:project_empty_repo, :public) } - set(:user) { create(:user) } - set(:issue) { create(:issue, project: project, author: user) } +describe "Issues > User edits issue", :js do + let_it_be(:project) { create(:project_empty_repo, :public) } + let_it_be(:user) { create(:user) } + let_it_be(:issue) { create(:issue, project: project, author: user, assignees: [user]) } + let_it_be(:label) { create(:label, project: project) } + let_it_be(:milestone) { create(:milestone, project: project) } before do project.add_developer(user) sign_in(user) + end + + context "from edit page" do + before do + visit edit_project_issue_path(project, issue) + end + + it "previews content" do + form = first(".gfm-form") + + page.within(form) do + fill_in("Description", with: "Bug fixed :smile:") + click_button("Preview") + end + + expect(form).to have_button("Write") + end + + it 'allows user to select unassigned' do + visit edit_project_issue_path(project, issue) + + expect(page).to have_content "Assignee #{user.name}" + + first('.js-user-search').click + click_link 'Unassigned' + + click_button 'Save changes' + + page.within('.assignee') do + expect(page).to have_content 'None - assign yourself' + end + end + + context 'with due date' do + before do + visit edit_project_issue_path(project, issue) + end + + it 'saves with due date' do + date = Date.today.at_beginning_of_month.tomorrow + + fill_in 'issue_title', with: 'bug 345' + fill_in 'issue_description', with: 'bug description' + find('#issuable-due-date').click + + page.within '.pika-single' do + click_button date.day + end + + expect(find('#issuable-due-date').value).to eq date.to_s + + click_button 'Save changes' - visit(edit_project_issue_path(project, issue)) + page.within '.issuable-sidebar' do + expect(page).to have_content date.to_s(:medium) + end + end + + it 'warns about version conflict' do + issue.update(title: "New title") + + fill_in 'issue_title', with: 'bug 345' + fill_in 'issue_description', with: 'bug description' + + click_button 'Save changes' + + expect(page).to have_content 'Someone edited the issue the same time you did' + end + end end - it "previews content" do - form = first(".gfm-form") + context "from issue#show" do + before do + visit project_issue_path(project, issue) + end + + describe 'update labels' do + it 'will not send ajax request when no data is changed' do + page.within '.labels' do + click_link 'Edit' - page.within(form) do - fill_in("Description", with: "Bug fixed :smile:") - click_button("Preview") + find('.dropdown-menu-close', match: :first).click + + expect(page).not_to have_selector('.block-loading') + end + end end - expect(form).to have_button("Write") + describe 'update assignee' do + context 'by authorized user' do + def close_dropdown_menu_if_visible + find('.dropdown-menu-toggle', visible: :all).tap do |toggle| + toggle.click if toggle.visible? + end + end + + it 'allows user to select unassigned' do + visit project_issue_path(project, issue) + + page.within('.assignee') do + expect(page).to have_content "#{user.name}" + + click_link 'Edit' + click_link 'Unassigned' + first('.title').click + expect(page).to have_content 'None - assign yourself' + end + end + + it 'allows user to select an assignee' do + issue2 = create(:issue, project: project, author: user) + visit project_issue_path(project, issue2) + + page.within('.assignee') do + expect(page).to have_content "None" + end + + page.within '.assignee' do + click_link 'Edit' + end + + page.within '.dropdown-menu-user' do + click_link user.name + end + + page.within('.assignee') do + expect(page).to have_content user.name + end + end + + it 'allows user to unselect themselves' do + issue2 = create(:issue, project: project, author: user) + + visit project_issue_path(project, issue2) + + page.within '.assignee' do + click_link 'Edit' + click_link user.name + + close_dropdown_menu_if_visible + + page.within '.value .author' do + expect(page).to have_content user.name + end + + click_link 'Edit' + click_link user.name + + close_dropdown_menu_if_visible + + page.within '.value .assign-yourself' do + expect(page).to have_content "None" + end + end + end + end + + context 'by unauthorized user' do + let(:guest) { create(:user) } + + before do + project.add_guest(guest) + end + + it 'shows assignee text' do + sign_out(:user) + sign_in(guest) + + visit project_issue_path(project, issue) + expect(page).to have_content issue.assignees.first.name + end + end + end + + describe 'update milestone' do + context 'by authorized user' do + it 'allows user to select unassigned' do + visit project_issue_path(project, issue) + + page.within('.milestone') do + expect(page).to have_content "None" + end + + find('.block.milestone .edit-link').click + sleep 2 # wait for ajax stuff to complete + first('.dropdown-content li').click + sleep 2 + page.within('.milestone') do + expect(page).to have_content 'None' + end + end + + it 'allows user to de-select milestone' do + visit project_issue_path(project, issue) + + page.within('.milestone') do + click_link 'Edit' + click_link milestone.title + + page.within '.value' do + expect(page).to have_content milestone.title + end + + click_link 'Edit' + click_link milestone.title + + page.within '.value' do + expect(page).to have_content 'None' + end + end + end + end + + context 'by unauthorized user' do + let(:guest) { create(:user) } + + before do + project.add_guest(guest) + issue.milestone = milestone + issue.save + end + + it 'shows milestone text' do + sign_out(:user) + sign_in(guest) + + visit project_issue_path(project, issue) + expect(page).to have_content milestone.title + end + end + end + + context 'update due date' do + it 'adds due date to issue' do + date = Date.today.at_beginning_of_month + 2.days + + page.within '.due_date' do + click_link 'Edit' + + page.within '.pika-single' do + click_button date.day + end + + wait_for_requests + + expect(find('.value').text).to have_content date.strftime('%b %-d, %Y') + end + end + + it 'removes due date from issue' do + date = Date.today.at_beginning_of_month + 2.days + + page.within '.due_date' do + click_link 'Edit' + + page.within '.pika-single' do + click_button date.day + end + + wait_for_requests + + expect(page).to have_no_content 'None' + + click_link 'remove due date' + expect(page).to have_content 'None' + end + end + end end end diff --git a/spec/features/issues/user_filters_issues_spec.rb b/spec/features/issues/user_filters_issues_spec.rb new file mode 100644 index 00000000000..714bc972025 --- /dev/null +++ b/spec/features/issues/user_filters_issues_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'User filters issues' do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project_empty_repo, :public) } + + before do + %w[foobar barbaz].each do |title| + create(:issue, + author: user, + assignees: [user], + project: project, + title: title) + end + + @issue = Issue.find_by(title: 'foobar') + @issue.milestone = create(:milestone, project: project) + @issue.assignees = [] + @issue.save + end + + let(:issue) { @issue } + + it 'allows filtering by issues with no specified assignee' do + visit project_issues_path(project, assignee_id: IssuableFinder::FILTER_NONE) + + expect(page).to have_content 'foobar' + expect(page).not_to have_content 'barbaz' + end + + it 'allows filtering by a specified assignee' do + visit project_issues_path(project, assignee_id: user.id) + + expect(page).not_to have_content 'foobar' + expect(page).to have_content 'barbaz' + end +end diff --git a/spec/features/issues/user_resets_their_incoming_email_token_spec.rb b/spec/features/issues/user_resets_their_incoming_email_token_spec.rb new file mode 100644 index 00000000000..108b6f550db --- /dev/null +++ b/spec/features/issues/user_resets_their_incoming_email_token_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Issues > User resets their incoming email token' do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public, namespace: user.namespace) } + let_it_be(:issue) { create(:issue, project: project) } + + before do + stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab") + project.add_maintainer(user) + sign_in(user) + + visit namespace_project_issues_path(user.namespace, project) + end + + it 'changes incoming email address token', :js do + find('.issuable-email-modal-btn').click + previous_token = find('input#issuable_email').value + find('.incoming-email-token-reset').click + + wait_for_requests + + expect(page).to have_no_field('issuable_email', with: previous_token) + new_token = project.new_issuable_address(user.reload, 'issue') + expect(page).to have_field( + 'issuable_email', + with: new_token + ) + end +end diff --git a/spec/features/issues/user_sees_breadcrumb_links_spec.rb b/spec/features/issues/user_sees_breadcrumb_links_spec.rb index f31d730c337..8a120a0a0b2 100644 --- a/spec/features/issues/user_sees_breadcrumb_links_spec.rb +++ b/spec/features/issues/user_sees_breadcrumb_links_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'New issue breadcrumb' do - let(:project) { create(:project) } + let_it_be(:project, reload: true) { create(:project) } let(:user) { project.creator } before do @@ -17,4 +17,22 @@ describe 'New issue breadcrumb' do expect(find_link('New')[:href]).to end_with(new_project_issue_path(project)) end end + + it 'links to current issue in breadcrubs' do + issue = create(:issue, project: project) + + visit project_issue_path(project, issue) + + expect(find('.breadcrumbs-sub-title a')[:href]).to end_with(issue_path(issue)) + end + + it 'excludes award_emoji from comment count' do + issue = create(:issue, author: user, assignees: [user], project: project, title: 'foobar') + create(:award_emoji, awardable: issue) + + visit project_issues_path(project, assignee_id: user.id) + + expect(page).to have_content 'foobar' + expect(page.all('.no-comments').first.text).to eq "0" + end end diff --git a/spec/features/issues/user_sees_empty_state_spec.rb b/spec/features/issues/user_sees_empty_state_spec.rb new file mode 100644 index 00000000000..114d119aca8 --- /dev/null +++ b/spec/features/issues/user_sees_empty_state_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Issues > User sees empty state' do + let_it_be(:project) { create(:project, :public) } + let_it_be(:user) { project.creator } + + shared_examples_for 'empty state with filters' do + it 'user sees empty state with filters' do + create(:issue, author: user, project: project) + + visit project_issues_path(project, milestone_title: "1.0") + + expect(page).to have_content('Sorry, your filter produced no results') + expect(page).to have_content('To widen your search, change or remove filters above') + end + end + + describe 'while user is signed out' do + describe 'empty state' do + it 'user sees empty state' do + visit project_issues_path(project) + + expect(page).to have_content('Register / Sign In') + expect(page).to have_content('The Issue Tracker is the place to add things that need to be improved or solved in a project.') + expect(page).to have_content('You can register or sign in to create issues for this project.') + end + + it_behaves_like 'empty state with filters' + end + end + + describe 'while user is signed in' do + before do + sign_in(user) + end + + describe 'empty state' do + it 'user sees empty state' do + visit project_issues_path(project) + + expect(page).to have_content('The Issue Tracker is the place to add things that need to be improved or solved in a project') + expect(page).to have_content('Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.') + expect(page).to have_content('New issue') + end + + it_behaves_like 'empty state with filters' + end + end +end diff --git a/spec/features/issues/user_sees_live_update_spec.rb b/spec/features/issues/user_sees_live_update_spec.rb new file mode 100644 index 00000000000..98c7d289fb0 --- /dev/null +++ b/spec/features/issues/user_sees_live_update_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Issues > User sees live update', :js do + let_it_be(:project) { create(:project, :public) } + let_it_be(:user) { project.creator } + + before do + sign_in(user) + end + + describe 'title issue#show' do + it 'updates the title' do + issue = create(:issue, author: user, assignees: [user], project: project, title: 'new title') + + visit project_issue_path(project, issue) + + expect(page).to have_text("new title") + + issue.update(title: "updated title") + + wait_for_requests + expect(page).to have_text("updated title") + end + end + + describe 'confidential issue#show' do + it 'shows confidential sibebar information as confidential and can be turned off' do + issue = create(:issue, :confidential, project: project) + + visit project_issue_path(project, issue) + + expect(page).to have_css('.issuable-note-warning') + expect(find('.issuable-sidebar-item.confidentiality')).to have_css('.is-active') + expect(find('.issuable-sidebar-item.confidentiality')).not_to have_css('.not-active') + + find('.confidential-edit').click + expect(page).to have_css('.sidebar-item-warning-message') + + within('.sidebar-item-warning-message') do + find('.btn-close').click + end + + wait_for_requests + + visit project_issue_path(project, issue) + + expect(page).not_to have_css('.is-active') + end + end +end diff --git a/spec/features/issues/user_sorts_issues_spec.rb b/spec/features/issues/user_sorts_issues_spec.rb index 79938785633..66110f55435 100644 --- a/spec/features/issues/user_sorts_issues_spec.rb +++ b/spec/features/issues/user_sorts_issues_spec.rb @@ -3,12 +3,17 @@ require "spec_helper" describe "User sorts issues" do - set(:user) { create(:user) } - set(:group) { create(:group) } - set(:project) { create(:project_empty_repo, :public, group: group) } - set(:issue1) { create(:issue, project: project) } - set(:issue2) { create(:issue, project: project) } - set(:issue3) { create(:issue, project: project) } + include SortingHelper + include IssueHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project_empty_repo, :public, group: group) } + let_it_be(:issue1, reload: true) { create(:issue, title: 'foo', created_at: Time.now, project: project) } + let_it_be(:issue2, reload: true) { create(:issue, title: 'bar', created_at: Time.now - 60, project: project) } + let_it_be(:issue3, reload: true) { create(:issue, title: 'baz', created_at: Time.now - 120, project: project) } + let_it_be(:newer_due_milestone) { create(:milestone, project: project, due_date: '2013-12-11') } + let_it_be(:later_due_milestone) { create(:milestone, project: project, due_date: '2013-12-12') } before do create_list(:award_emoji, 2, :upvote, awardable: issue1) @@ -62,4 +67,174 @@ describe "User sorts issues" do end end end + + it 'sorts by newest' do + visit project_issues_path(project, sort: sort_value_created_date) + + expect(first_issue).to include('foo') + expect(last_issue).to include('baz') + end + + it 'sorts by most recently updated' do + issue3.updated_at = Time.now + 100 + issue3.save + visit project_issues_path(project, sort: sort_value_recently_updated) + + expect(first_issue).to include('baz') + end + + describe 'sorting by due date' do + before do + issue1.update(due_date: 1.day.from_now) + issue2.update(due_date: 6.days.from_now) + end + + it 'sorts by due date' do + visit project_issues_path(project, sort: sort_value_due_date) + + expect(first_issue).to include('foo') + end + + it 'sorts by due date by excluding nil due dates' do + issue2.update(due_date: nil) + + visit project_issues_path(project, sort: sort_value_due_date) + + expect(first_issue).to include('foo') + end + + context 'with a filter on labels' do + let(:label) { create(:label, project: project) } + + before do + create(:label_link, label: label, target: issue1) + end + + it 'sorts by least recently due date by excluding nil due dates' do + issue2.update(due_date: nil) + + visit project_issues_path(project, label_names: [label.name], sort: sort_value_due_date_later) + + expect(first_issue).to include('foo') + end + end + end + + describe 'filtering by due date' do + before do + issue1.update(due_date: 1.day.from_now) + issue2.update(due_date: 6.days.from_now) + end + + it 'filters by none' do + visit project_issues_path(project, due_date: Issue::NoDueDate.name) + + page.within '.issues-holder' do + expect(page).not_to have_content('foo') + expect(page).not_to have_content('bar') + expect(page).to have_content('baz') + end + end + + it 'filters by any' do + visit project_issues_path(project, due_date: Issue::AnyDueDate.name) + + page.within '.issues-holder' do + expect(page).to have_content('foo') + expect(page).to have_content('bar') + expect(page).to have_content('baz') + end + end + + it 'filters by due this week' do + issue1.update(due_date: Date.today.beginning_of_week + 2.days) + issue2.update(due_date: Date.today.end_of_week) + issue3.update(due_date: Date.today - 8.days) + + visit project_issues_path(project, due_date: Issue::DueThisWeek.name) + + page.within '.issues-holder' do + expect(page).to have_content('foo') + expect(page).to have_content('bar') + expect(page).not_to have_content('baz') + end + end + + it 'filters by due this month' do + issue1.update(due_date: Date.today.beginning_of_month + 2.days) + issue2.update(due_date: Date.today.end_of_month) + issue3.update(due_date: Date.today - 50.days) + + visit project_issues_path(project, due_date: Issue::DueThisMonth.name) + + page.within '.issues-holder' do + expect(page).to have_content('foo') + expect(page).to have_content('bar') + expect(page).not_to have_content('baz') + end + end + + it 'filters by overdue' do + issue1.update(due_date: Date.today + 2.days) + issue2.update(due_date: Date.today + 20.days) + issue3.update(due_date: Date.yesterday) + + visit project_issues_path(project, due_date: Issue::Overdue.name) + + page.within '.issues-holder' do + expect(page).not_to have_content('foo') + expect(page).not_to have_content('bar') + expect(page).to have_content('baz') + end + end + + it 'filters by due next month and previous two weeks' do + issue1.update(due_date: Date.today - 4.weeks) + issue2.update(due_date: (Date.today + 2.months).beginning_of_month) + issue3.update(due_date: Date.yesterday) + + visit project_issues_path(project, due_date: Issue::DueNextMonthAndPreviousTwoWeeks.name) + + page.within '.issues-holder' do + expect(page).not_to have_content('foo') + expect(page).not_to have_content('bar') + expect(page).to have_content('baz') + end + end + end + + describe 'sorting by milestone' do + before do + issue1.milestone = newer_due_milestone + issue1.save + issue2.milestone = later_due_milestone + issue2.save + end + + it 'sorts by milestone' do + visit project_issues_path(project, sort: sort_value_milestone) + + expect(first_issue).to include('foo') + expect(last_issue).to include('baz') + end + end + + describe 'combine filter and sort' do + let(:user2) { create(:user) } + + before do + issue1.assignees << user2 + issue1.save + issue2.assignees << user2 + issue2.save + end + + it 'sorts with a filter applied' do + visit project_issues_path(project, sort: sort_value_created_date, assignee_id: user2.id) + + expect(first_issue).to include('foo') + expect(last_issue).to include('bar') + expect(page).not_to have_content('baz') + end + end end diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb deleted file mode 100644 index ef9daf70b0c..00000000000 --- a/spec/features/issues_spec.rb +++ /dev/null @@ -1,828 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe 'Issues' do - include DropzoneHelper - include IssueHelpers - include SortingHelper - - let(:user) { create(:user) } - let(:project) { create(:project, :public) } - - shared_examples_for 'empty state with filters' do - it 'user sees empty state with filters' do - create(:issue, author: user, project: project) - - visit project_issues_path(project, milestone_title: "1.0") - - expect(page).to have_content('Sorry, your filter produced no results') - expect(page).to have_content('To widen your search, change or remove filters above') - end - end - - describe 'while user is signed out' do - describe 'empty state' do - it 'user sees empty state' do - visit project_issues_path(project) - - expect(page).to have_content('Register / Sign In') - expect(page).to have_content('The Issue Tracker is the place to add things that need to be improved or solved in a project.') - expect(page).to have_content('You can register or sign in to create issues for this project.') - end - - it_behaves_like 'empty state with filters' - end - end - - describe 'while user is signed in' do - before do - sign_in(user) - user2 = create(:user) - - project.add_developer(user) - project.add_developer(user2) - end - - describe 'empty state' do - it 'user sees empty state' do - visit project_issues_path(project) - - expect(page).to have_content('The Issue Tracker is the place to add things that need to be improved or solved in a project') - expect(page).to have_content('Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.') - expect(page).to have_content('New issue') - end - - it_behaves_like 'empty state with filters' - end - - describe 'Edit issue' do - let!(:issue) do - create(:issue, - author: user, - assignees: [user], - project: project) - end - - before do - visit edit_project_issue_path(project, issue) - find('.js-zen-enter').click - end - - it 'opens new issue popup' do - expect(page).to have_content("Issue ##{issue.iid}") - end - end - - describe 'Editing issue assignee' do - let!(:issue) do - create(:issue, - author: user, - assignees: [user], - project: project) - end - - it 'allows user to select unassigned', :js do - visit edit_project_issue_path(project, issue) - - expect(page).to have_content "Assignee #{user.name}" - - first('.js-user-search').click - click_link 'Unassigned' - - click_button 'Save changes' - - page.within('.assignee') do - expect(page).to have_content 'None - assign yourself' - end - - expect(issue.reload.assignees).to be_empty - end - end - - describe 'due date', :js do - context 'on new form' do - before do - visit new_project_issue_path(project) - end - - it 'saves with due date' do - date = Date.today.at_beginning_of_month - - fill_in 'issue_title', with: 'bug 345' - fill_in 'issue_description', with: 'bug description' - find('#issuable-due-date').click - - page.within '.pika-single' do - click_button date.day - end - - expect(find('#issuable-due-date').value).to eq date.to_s - - click_button 'Submit issue' - - page.within '.issuable-sidebar' do - expect(page).to have_content date.to_s(:medium) - end - end - end - - context 'on edit form' do - let(:issue) { create(:issue, author: user, project: project, due_date: Date.today.at_beginning_of_month.to_s) } - - before do - visit edit_project_issue_path(project, issue) - end - - it 'saves with due date' do - date = Date.today.at_beginning_of_month - - 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 '.pika-single' do - click_button date.day - end - - expect(find('#issuable-due-date').value).to eq date.to_s - - click_button 'Save changes' - - page.within '.issuable-sidebar' do - expect(page).to have_content date.to_s(:medium) - end - end - - it 'warns about version conflict' do - issue.update(title: "New title") - - fill_in 'issue_title', with: 'bug 345' - fill_in 'issue_description', with: 'bug description' - - click_button 'Save changes' - - expect(page).to have_content 'Someone edited the issue the same time you did' - end - end - end - - describe 'Issue info' do - it 'links to current issue in breadcrubs' do - issue = create(:issue, project: project) - - visit project_issue_path(project, issue) - - expect(find('.breadcrumbs-sub-title a')[:href]).to end_with(issue_path(issue)) - end - - it 'excludes award_emoji from comment count' do - issue = create(:issue, author: user, assignees: [user], project: project, title: 'foobar') - create(:award_emoji, awardable: issue) - - visit project_issues_path(project, assignee_id: user.id) - - expect(page).to have_content 'foobar' - expect(page.all('.no-comments').first.text).to eq "0" - end - end - - describe 'Filter issue' do - before do - %w(foobar barbaz gitlab).each do |title| - create(:issue, - author: user, - assignees: [user], - project: project, - title: title) - end - - @issue = Issue.find_by(title: 'foobar') - @issue.milestone = create(:milestone, project: project) - @issue.assignees = [] - @issue.save - end - - let(:issue) { @issue } - - it 'allows filtering by issues with no specified assignee' do - visit project_issues_path(project, assignee_id: IssuableFinder::FILTER_NONE) - - expect(page).to have_content 'foobar' - expect(page).not_to have_content 'barbaz' - expect(page).not_to have_content 'gitlab' - end - - it 'allows filtering by a specified assignee' do - visit project_issues_path(project, assignee_id: user.id) - - expect(page).not_to have_content 'foobar' - expect(page).to have_content 'barbaz' - expect(page).to have_content 'gitlab' - end - end - - describe 'filter issue' do - titles = %w[foo bar baz] - titles.each_with_index do |title, index| - let!(title.to_sym) do - create(:issue, title: title, - project: project, - created_at: Time.now - (index * 60)) - end - end - let(:newer_due_milestone) { create(:milestone, project: project, due_date: '2013-12-11') } - let(:later_due_milestone) { create(:milestone, project: project, due_date: '2013-12-12') } - - it 'sorts by newest' do - visit project_issues_path(project, sort: sort_value_created_date) - - expect(first_issue).to include('foo') - expect(last_issue).to include('baz') - end - - it 'sorts by most recently updated' do - baz.updated_at = Time.now + 100 - baz.save - visit project_issues_path(project, sort: sort_value_recently_updated) - - expect(first_issue).to include('baz') - end - - describe 'sorting by due date' do - before do - foo.update(due_date: 1.day.from_now) - bar.update(due_date: 6.days.from_now) - end - - it 'sorts by due date' do - visit project_issues_path(project, sort: sort_value_due_date) - - expect(first_issue).to include('foo') - end - - it 'sorts by due date by excluding nil due dates' do - bar.update(due_date: nil) - - visit project_issues_path(project, sort: sort_value_due_date) - - expect(first_issue).to include('foo') - end - - context 'with a filter on labels' do - let(:label) { create(:label, project: project) } - - before do - create(:label_link, label: label, target: foo) - end - - it 'sorts by least recently due date by excluding nil due dates' do - bar.update(due_date: nil) - - visit project_issues_path(project, label_names: [label.name], sort: sort_value_due_date_later) - - expect(first_issue).to include('foo') - end - end - end - - describe 'filtering by due date' do - before do - foo.update(due_date: 1.day.from_now) - bar.update(due_date: 6.days.from_now) - end - - it 'filters by none' do - visit project_issues_path(project, due_date: Issue::NoDueDate.name) - - page.within '.issues-holder' do - expect(page).not_to have_content('foo') - expect(page).not_to have_content('bar') - expect(page).to have_content('baz') - end - end - - it 'filters by any' do - visit project_issues_path(project, due_date: Issue::AnyDueDate.name) - - page.within '.issues-holder' do - expect(page).to have_content('foo') - expect(page).to have_content('bar') - expect(page).to have_content('baz') - end - end - - it 'filters by due this week' do - foo.update(due_date: Date.today.beginning_of_week + 2.days) - bar.update(due_date: Date.today.end_of_week) - baz.update(due_date: Date.today - 8.days) - - visit project_issues_path(project, due_date: Issue::DueThisWeek.name) - - page.within '.issues-holder' do - expect(page).to have_content('foo') - expect(page).to have_content('bar') - expect(page).not_to have_content('baz') - end - end - - it 'filters by due this month' do - foo.update(due_date: Date.today.beginning_of_month + 2.days) - bar.update(due_date: Date.today.end_of_month) - baz.update(due_date: Date.today - 50.days) - - visit project_issues_path(project, due_date: Issue::DueThisMonth.name) - - page.within '.issues-holder' do - expect(page).to have_content('foo') - expect(page).to have_content('bar') - expect(page).not_to have_content('baz') - end - end - - it 'filters by overdue' do - foo.update(due_date: Date.today + 2.days) - bar.update(due_date: Date.today + 20.days) - baz.update(due_date: Date.yesterday) - - visit project_issues_path(project, due_date: Issue::Overdue.name) - - page.within '.issues-holder' do - expect(page).not_to have_content('foo') - expect(page).not_to have_content('bar') - expect(page).to have_content('baz') - end - end - - it 'filters by due next month and previous two weeks' do - foo.update(due_date: Date.today - 4.weeks) - bar.update(due_date: (Date.today + 2.months).beginning_of_month) - baz.update(due_date: Date.yesterday) - - visit project_issues_path(project, due_date: Issue::DueNextMonthAndPreviousTwoWeeks.name) - - page.within '.issues-holder' do - expect(page).not_to have_content('foo') - expect(page).not_to have_content('bar') - expect(page).to have_content('baz') - end - end - end - - describe 'sorting by milestone' do - before do - foo.milestone = newer_due_milestone - foo.save - bar.milestone = later_due_milestone - bar.save - end - - it 'sorts by milestone' do - visit project_issues_path(project, sort: sort_value_milestone) - - expect(first_issue).to include('foo') - expect(last_issue).to include('baz') - end - end - - describe 'combine filter and sort' do - let(:user2) { create(:user) } - - before do - foo.assignees << user2 - foo.save - bar.assignees << user2 - bar.save - end - - it 'sorts with a filter applied' do - visit project_issues_path(project, sort: sort_value_created_date, assignee_id: user2.id) - - expect(first_issue).to include('foo') - expect(last_issue).to include('bar') - expect(page).not_to have_content('baz') - end - end - end - - describe 'when I want to reset my incoming email token' do - let(:project1) { create(:project, namespace: user.namespace) } - let!(:issue) { create(:issue, project: project1) } - - before do - stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab") - project1.add_maintainer(user) - visit namespace_project_issues_path(user.namespace, project1) - end - - it 'changes incoming email address token', :js do - find('.issuable-email-modal-btn').click - previous_token = find('input#issuable_email').value - find('.incoming-email-token-reset').click - - wait_for_requests - - expect(page).to have_no_field('issuable_email', with: previous_token) - new_token = project1.new_issuable_address(user.reload, 'issue') - expect(page).to have_field( - 'issuable_email', - with: new_token - ) - end - end - - describe 'update labels from issue#show', :js do - let(:issue) { create(:issue, project: project, author: user, assignees: [user]) } - let!(:label) { create(:label, project: project) } - - before do - visit project_issue_path(project, issue) - end - - it 'will not send ajax request when no data is changed' do - page.within '.labels' do - click_link 'Edit' - - find('.dropdown-menu-close', match: :first).click - - expect(page).not_to have_selector('.block-loading') - end - end - end - - describe 'update assignee from issue#show' do - let(:issue) { create(:issue, project: project, author: user, assignees: [user]) } - - context 'by authorized user' do - it 'allows user to select unassigned', :js do - visit project_issue_path(project, issue) - - page.within('.assignee') do - expect(page).to have_content "#{user.name}" - - click_link 'Edit' - click_link 'Unassigned' - first('.title').click - expect(page).to have_content 'None' - end - - wait_for_requests - - expect(issue.reload.assignees).to be_empty - end - - it 'allows user to select an assignee', :js do - issue2 = create(:issue, project: project, author: user) - visit project_issue_path(project, issue2) - - page.within('.assignee') do - expect(page).to have_content "None" - end - - page.within '.assignee' do - click_link 'Edit' - end - - page.within '.dropdown-menu-user' do - click_link user.name - end - - page.within('.assignee') do - expect(page).to have_content user.name - end - end - - it 'allows user to unselect themselves', :js do - issue2 = create(:issue, project: project, author: user) - - visit project_issue_path(project, issue2) - - def close_dropdown_menu_if_visible - find('.dropdown-menu-toggle', visible: :all).tap do |toggle| - toggle.click if toggle.visible? - end - end - - page.within '.assignee' do - click_link 'Edit' - click_link user.name - - close_dropdown_menu_if_visible - - page.within '.value .author' do - expect(page).to have_content user.name - end - - click_link 'Edit' - click_link user.name - - close_dropdown_menu_if_visible - - page.within '.value .assign-yourself' do - expect(page).to have_content "None" - end - end - end - end - - context 'by unauthorized user' do - let(:guest) { create(:user) } - - before do - project.add_guest(guest) - end - - it 'shows assignee text', :js do - sign_out(:user) - sign_in(guest) - - visit project_issue_path(project, issue) - expect(page).to have_content issue.assignees.first.name - end - end - end - - describe 'update milestone from issue#show' do - let!(:issue) { create(:issue, project: project, author: user) } - let!(:milestone) { create(:milestone, project: project) } - - context 'by authorized user' do - it 'allows user to select unassigned', :js do - visit project_issue_path(project, issue) - - page.within('.milestone') do - expect(page).to have_content "None" - end - - find('.block.milestone .edit-link').click - sleep 2 # wait for ajax stuff to complete - first('.dropdown-content li').click - sleep 2 - page.within('.milestone') do - expect(page).to have_content 'None' - end - - expect(issue.reload.milestone).to be_nil - end - - it 'allows user to de-select milestone', :js do - visit project_issue_path(project, issue) - - page.within('.milestone') do - click_link 'Edit' - click_link milestone.title - - page.within '.value' do - expect(page).to have_content milestone.title - end - - click_link 'Edit' - click_link milestone.title - - page.within '.value' do - expect(page).to have_content 'None' - end - end - end - end - - context 'by unauthorized user' do - let(:guest) { create(:user) } - - before do - project.add_guest(guest) - issue.milestone = milestone - issue.save - end - - it 'shows milestone text', :js do - sign_out(:user) - sign_in(guest) - - visit project_issue_path(project, issue) - expect(page).to have_content milestone.title - end - end - end - - describe 'new issue' do - let!(:issue) { create(:issue, project: project) } - - context 'by unauthenticated user' do - before do - sign_out(:user) - end - - it 'redirects to signin then back to new issue after signin' do - visit project_issues_path(project) - - page.within '.nav-controls' do - click_link 'New issue' - end - - expect(current_path).to eq new_user_session_path - - gitlab_sign_in(create(:user)) - - expect(current_path).to eq new_project_issue_path(project) - end - end - - it 'clears local storage after creating a new issue', :js do - 2.times do - visit new_project_issue_path(project) - wait_for_requests - - expect(page).to have_field('Title', with: '') - - fill_in 'issue_title', with: 'bug 345' - fill_in 'issue_description', with: 'bug description' - - click_button 'Submit issue' - end - end - - context 'dropzone upload file', :js do - before do - visit new_project_issue_path(project) - end - - it 'uploads file when dragging into textarea' do - dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') - - expect(page.find_field("issue_description").value).to have_content 'banana_sample' - end - - it "doesn't add double newline to end of a single attachment markdown" do - dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') - - expect(page.find_field("issue_description").value).not_to match /\n\n$/ - end - - it "cancels a file upload correctly" do - slow_requests do - dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false) - - click_button 'Cancel' - end - - expect(page).to have_button('Attach a file') - expect(page).not_to have_button('Cancel') - expect(page).not_to have_selector('.uploading-progress-container', visible: true) - end - end - - context 'form filled by URL parameters' do - let(:project) { create(:project, :public, :repository) } - - before do - project.repository.create_file( - user, - '.gitlab/issue_templates/bug.md', - 'this is a test "bug" template', - message: 'added issue template', - branch_name: 'master') - - visit new_project_issue_path(project, issuable_template: 'bug') - end - - it 'fills in template' do - expect(find('.js-issuable-selector .dropdown-toggle-text')).to have_content('bug') - end - end - - context 'suggestions', :js do - it 'displays list of related issues' do - create(:issue, project: project, title: 'test issue') - - visit new_project_issue_path(project) - - fill_in 'issue_title', with: issue.title - - expect(page).to have_selector('.suggestion-item', count: 1) - end - end - end - - describe 'new issue by email' do - shared_examples 'show the email in the modal' do - let(:issue) { create(:issue, project: project) } - - before do - project.issues << issue - stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab") - - visit project_issues_path(project) - click_button('Email a new issue') - end - - it 'click the button to show modal for the new email' do - page.within '#issuable-email-modal' do - email = project.new_issuable_address(user, 'issue') - - expect(page).to have_selector("input[value='#{email}']") - end - end - end - - context 'with existing issues' do - let!(:issue) { create(:issue, project: project, author: user) } - - it_behaves_like 'show the email in the modal' - end - - context 'without existing issues' do - it_behaves_like 'show the email in the modal' - end - end - - describe 'due date' do - context 'update due on issue#show', :js do - let(:issue) { create(:issue, project: project, author: user, assignees: [user]) } - - before do - visit project_issue_path(project, issue) - end - - it 'adds due date to issue' do - date = Date.today.at_beginning_of_month + 2.days - - page.within '.due_date' do - click_link 'Edit' - - page.within '.pika-single' do - click_button date.day - end - - wait_for_requests - - expect(find('.value').text).to have_content date.strftime('%b %-d, %Y') - end - end - - it 'removes due date from issue' do - date = Date.today.at_beginning_of_month + 2.days - - page.within '.due_date' do - click_link 'Edit' - - page.within '.pika-single' do - click_button date.day - end - - wait_for_requests - - expect(page).to have_no_content 'None' - - click_link 'remove due date' - expect(page).to have_content 'None' - end - end - end - end - - describe 'title issue#show', :js do - it 'updates the title', :js do - issue = create(:issue, author: user, assignees: [user], project: project, title: 'new title') - - visit project_issue_path(project, issue) - - expect(page).to have_text("new title") - - issue.update(title: "updated title") - - wait_for_requests - expect(page).to have_text("updated title") - end - end - - describe 'confidential issue#show', :js do - it 'shows confidential sibebar information as confidential and can be turned off' do - issue = create(:issue, :confidential, project: project) - - visit project_issue_path(project, issue) - - expect(page).to have_css('.issuable-note-warning') - expect(find('.issuable-sidebar-item.confidentiality')).to have_css('.is-active') - expect(find('.issuable-sidebar-item.confidentiality')).not_to have_css('.not-active') - - find('.confidential-edit').click - expect(page).to have_css('.sidebar-item-warning-message') - - within('.sidebar-item-warning-message') do - find('.btn-close').click - end - - wait_for_requests - - visit project_issue_path(project, issue) - - expect(page).not_to have_css('.is-active') - end - end - end -end diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb index cab86f3fd94..cd62bab412a 100644 --- a/spec/features/merge_request/user_sees_versions_spec.rb +++ b/spec/features/merge_request/user_sees_versions_spec.rb @@ -50,7 +50,7 @@ describe 'Merge request > User sees versions', :js do expect(page).to have_content 'latest version' end - expect(page).to have_content '8 Files' + expect(page).to have_content '8 files' end it_behaves_like 'allows commenting', @@ -84,7 +84,7 @@ describe 'Merge request > User sees versions', :js do end it 'shows comments that were last relevant at that version' do - expect(page).to have_content '5 Files' + expect(page).to have_content '5 files' position = Gitlab::Diff::Position.new( old_path: ".gitmodules", @@ -128,12 +128,10 @@ describe 'Merge request > User sees versions', :js do diff_id: merge_request_diff3.id, start_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9' ) - expect(page).to have_content '4 Files' + expect(page).to have_content '4 files' - additions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group svg.ic-file-addition') - .ancestor('.diff-stats-group').text - deletions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group svg.ic-file-deletion') - .ancestor('.diff-stats-group').text + additions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group .js-file-addition-line').text + deletions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group .js-file-deletion-line').text expect(additions_content).to eq '15' expect(deletions_content).to eq '6' @@ -156,12 +154,10 @@ describe 'Merge request > User sees versions', :js do end it 'show diff between new and old version' do - additions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group svg.ic-file-addition') - .ancestor('.diff-stats-group').text - deletions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group svg.ic-file-deletion') - .ancestor('.diff-stats-group').text + additions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group .js-file-addition-line').text + deletions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group .js-file-deletion-line').text - expect(page).to have_content '4 Files' + expect(page).to have_content '4 files' expect(additions_content).to eq '15' expect(deletions_content).to eq '6' end @@ -171,7 +167,7 @@ describe 'Merge request > User sees versions', :js do page.within '.mr-version-dropdown' do expect(page).to have_content 'latest version' end - expect(page).to have_content '8 Files' + expect(page).to have_content '8 files' end it_behaves_like 'allows commenting', @@ -197,7 +193,7 @@ describe 'Merge request > User sees versions', :js do find('.btn-default').click click_link 'version 1' end - expect(page).to have_content '0 Files' + expect(page).to have_content '0 files' end end @@ -223,7 +219,7 @@ describe 'Merge request > User sees versions', :js do expect(page).to have_content 'version 1' end - expect(page).to have_content '0 Files' + expect(page).to have_content '0 files' end end diff --git a/spec/features/projects/settings/registry_settings_spec.rb b/spec/features/projects/settings/registry_settings_spec.rb new file mode 100644 index 00000000000..86da866a927 --- /dev/null +++ b/spec/features/projects/settings/registry_settings_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Project > Settings > CI/CD > Container registry tag expiration policy', :js do + let(:user) { create(:user) } + let(:project) { create(:project, namespace: user.namespace) } + + context 'as owner' do + before do + sign_in(user) + visit project_settings_ci_cd_path(project) + end + + it 'section is available' do + settings_block = find('#js-registry-policies') + expect(settings_block).to have_text 'Container Registry tag expiration policy' + end + + it 'Save expiration policy submit the form', :js do + within '#js-registry-policies' do + within '.card-body' do + click_button(class: 'gl-toggle') + select('7 days until tags are automatically removed', from: 'expiration-policy-interval') + select('Every day', from: 'expiration-policy-schedule') + select('50 tags per image name', from: 'expiration-policy-latest') + fill_in('expiration-policy-name-matching', with: '*-production') + end + submit_button = find('.card-footer .btn.btn-success') + expect(submit_button).not_to be_disabled + submit_button.click + end + flash_text = find('.flash-text') + expect(flash_text).to have_content('Expiration policy successfully saved.') + end + end +end diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb index 99285011405..7e0ee861b18 100644 --- a/spec/features/projects/tree/create_directory_spec.rb +++ b/spec/features/projects/tree/create_directory_spec.rb @@ -46,8 +46,6 @@ describe 'Multi-file editor new directory', :js do find('.js-ide-commit-mode').click - click_button 'Stage' - fill_in('commit-message', with: 'commit message ide') find(:css, ".js-ide-commit-new-mr input").set(false) diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb index 780575a5975..eba33168006 100644 --- a/spec/features/projects/tree/create_file_spec.rb +++ b/spec/features/projects/tree/create_file_spec.rb @@ -36,8 +36,6 @@ describe 'Multi-file editor new file', :js do find('.js-ide-commit-mode').click - click_button 'Stage' - fill_in('commit-message', with: 'commit message ide') find(:css, ".js-ide-commit-new-mr input").set(false) diff --git a/spec/frontend/diffs/components/compare_versions_spec.js b/spec/frontend/diffs/components/compare_versions_spec.js index 7f47677f56c..7648c39976c 100644 --- a/spec/frontend/diffs/components/compare_versions_spec.js +++ b/spec/frontend/diffs/components/compare_versions_spec.js @@ -50,8 +50,7 @@ describe('CompareVersions', () => { expect(treeListBtn.exists()).toBe(true); expect(treeListBtn.attributes('title')).toBe('Hide file browser'); - expect(treeListBtn.findAll(Icon).length).not.toBe(0); - expect(treeListBtn.find(Icon).props('name')).toBe('collapse-left'); + expect(treeListBtn.find(Icon).props('name')).toBe('file-tree'); }); it('should render comparison dropdowns with correct values', () => { diff --git a/spec/frontend/diffs/components/diff_stats_spec.js b/spec/frontend/diffs/components/diff_stats_spec.js index 4482abf18c1..aa5c7f6278a 100644 --- a/spec/frontend/diffs/components/diff_stats_spec.js +++ b/spec/frontend/diffs/components/diff_stats_spec.js @@ -1,5 +1,4 @@ import { shallowMount } from '@vue/test-utils'; -import Icon from '~/vue_shared/components/icon.vue'; import DiffStats from '~/diffs/components/diff_stats.vue'; describe('diff_stats', () => { @@ -24,18 +23,11 @@ describe('diff_stats', () => { }, }); - const findIcon = name => - wrapper - .findAll(Icon) - .filter(c => c.attributes('name') === name) - .at(0).element.parentNode; + const findFileLine = name => wrapper.find(name); + const additions = findFileLine('.js-file-addition-line'); + const deletions = findFileLine('.js-file-deletion-line'); - const additions = findIcon('file-addition'); - const deletions = findIcon('file-deletion'); - const filesChanged = findIcon('doc-code'); - - expect(additions.textContent).toContain('100'); - expect(deletions.textContent).toContain('200'); - expect(filesChanged.textContent).toContain('300'); + expect(additions.text()).toBe('100'); + expect(deletions.text()).toBe('200'); }); }); diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js index e7b34aa3e7a..a8e48f0b85e 100644 --- a/spec/frontend/ide/stores/actions/file_spec.js +++ b/spec/frontend/ide/stores/actions/file_spec.js @@ -514,6 +514,8 @@ describe('IDE store file actions', () => { describe('changeFileContent', () => { let tmpFile; + const callAction = (content = 'content\n') => + store.dispatch('changeFileContent', { path: tmpFile.path, content }); beforeEach(() => { tmpFile = file('tmpFile'); @@ -523,11 +525,7 @@ describe('IDE store file actions', () => { }); it('updates file content', done => { - store - .dispatch('changeFileContent', { - path: tmpFile.path, - content: 'content\n', - }) + callAction() .then(() => { expect(tmpFile.content).toBe('content\n'); @@ -537,11 +535,7 @@ describe('IDE store file actions', () => { }); it('adds a newline to the end of the file if it doesnt already exist', done => { - store - .dispatch('changeFileContent', { - path: tmpFile.path, - content: 'content', - }) + callAction('content') .then(() => { expect(tmpFile.content).toBe('content\n'); @@ -551,11 +545,7 @@ describe('IDE store file actions', () => { }); it('adds file into changedFiles array', done => { - store - .dispatch('changeFileContent', { - path: tmpFile.path, - content: 'content', - }) + callAction() .then(() => { expect(store.state.changedFiles.length).toBe(1); @@ -564,7 +554,7 @@ describe('IDE store file actions', () => { .catch(done.fail); }); - it('adds file once into changedFiles array', done => { + it('adds file not more than once into changedFiles array', done => { store .dispatch('changeFileContent', { path: tmpFile.path, @@ -604,6 +594,52 @@ describe('IDE store file actions', () => { .catch(done.fail); }); + describe('when `gon.feature.stageAllByDefault` is true', () => { + const originalGonFeatures = Object.assign({}, gon.features); + + beforeAll(() => { + gon.features = { stageAllByDefault: true }; + }); + + afterAll(() => { + gon.features = originalGonFeatures; + }); + + it('adds file into stagedFiles array', done => { + store + .dispatch('changeFileContent', { + path: tmpFile.path, + content: 'content', + }) + .then(() => { + expect(store.state.stagedFiles.length).toBe(1); + + done(); + }) + .catch(done.fail); + }); + + it('adds file not more than once into stagedFiles array', done => { + store + .dispatch('changeFileContent', { + path: tmpFile.path, + content: 'content', + }) + .then(() => + store.dispatch('changeFileContent', { + path: tmpFile.path, + content: 'content 123', + }), + ) + .then(() => { + expect(store.state.stagedFiles.length).toBe(1); + + done(); + }) + .catch(done.fail); + }); + }); + it('bursts unused seal', done => { store .dispatch('changeFileContent', { diff --git a/spec/frontend/registry/settings/components/__snapshots__/settings_form_spec.js.snap b/spec/frontend/registry/settings/components/__snapshots__/settings_form_spec.js.snap index bef4674bd8b..d26df308b97 100644 --- a/spec/frontend/registry/settings/components/__snapshots__/settings_form_spec.js.snap +++ b/spec/frontend/registry/settings/components/__snapshots__/settings_form_spec.js.snap @@ -106,7 +106,7 @@ exports[`Settings Form renders 1`] = ` <glformgroup-stub id="expiration-policy-latest-group" - label="Expiration latest:" + label="Number of tags to retain:" label-align="right" label-cols="3" label-for="expiration-policy-latest" @@ -136,7 +136,7 @@ exports[`Settings Form renders 1`] = ` <glformgroup-stub id="expiration-policy-name-matching-group" invalid-feedback="The value of this input should be less than 255 characters" - label="Expire Docker tags with name matching:" + label="Expire Docker tags that match this regex:" label-align="right" label-cols="3" label-for="expiration-policy-name-matching" diff --git a/spec/frontend/registry/settings/store/actions_spec.js b/spec/frontend/registry/settings/store/actions_spec.js index 71c815cd19c..80fb800ac3a 100644 --- a/spec/frontend/registry/settings/store/actions_spec.js +++ b/spec/frontend/registry/settings/store/actions_spec.js @@ -44,7 +44,9 @@ describe('Actions Registry Store', () => { }; const payload = { - tag_expiration_policies: 'foo', + data: { + container_expiration_policy: 'foo', + }, }; it('should fetch the data from the API', done => { @@ -56,7 +58,7 @@ describe('Actions Registry Store', () => { [], [ { type: 'toggleLoading' }, - { type: 'receiveSettingsSuccess', payload: payload.tag_expiration_policies }, + { type: 'receiveSettingsSuccess', payload: payload.data.container_expiration_policy }, { type: 'toggleLoading' }, ], done, @@ -83,7 +85,9 @@ describe('Actions Registry Store', () => { }; const payload = { - tag_expiration_policies: 'foo', + data: { + tag_expiration_policies: 'foo', + }, }; it('should fetch the data from the API', done => { @@ -95,11 +99,11 @@ describe('Actions Registry Store', () => { [], [ { type: 'toggleLoading' }, - { type: 'receiveSettingsSuccess', payload: payload.tag_expiration_policies }, + { type: 'receiveSettingsSuccess', payload: payload.data.container_expiration_policy }, { type: 'toggleLoading' }, ], () => { - expect(createFlash).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE); + expect(createFlash).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE, 'success'); done(); }, ); diff --git a/spec/frontend/repository/components/last_commit_spec.js b/spec/frontend/repository/components/last_commit_spec.js index 30f701ed77a..d2576ec26b7 100644 --- a/spec/frontend/repository/components/last_commit_spec.js +++ b/spec/frontend/repository/components/last_commit_spec.js @@ -6,7 +6,7 @@ import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link let vm; function createCommitData(data = {}) { - return { + const defaultData = { sha: '123456789', title: 'Commit title', message: 'Commit message', @@ -26,8 +26,8 @@ function createCommitData(data = {}) { group: {}, }, }, - ...data, }; + return Object.assign(defaultData, data); } function factory(commit = createCommitData(), loading = false) { @@ -46,6 +46,8 @@ function factory(commit = createCommitData(), loading = false) { vm.vm.$apollo.queries.commit.loading = loading; } +const emptyMessageClass = 'font-italic'; + describe('Repository last commit component', () => { afterEach(() => { vm.destroy(); @@ -135,4 +137,12 @@ describe('Repository last commit component', () => { expect(vm.element).toMatchSnapshot(); }); }); + + it('sets correct CSS class if the commit message is empty', () => { + factory(createCommitData({ message: '' })); + + return vm.vm.$nextTick().then(() => { + expect(vm.find('.item-title').classes()).toContain(emptyMessageClass); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/changed_file_icon_spec.js b/spec/frontend/vue_shared/components/changed_file_icon_spec.js index 9197cb8bc00..3a52941a06e 100644 --- a/spec/frontend/vue_shared/components/changed_file_icon_spec.js +++ b/spec/frontend/vue_shared/components/changed_file_icon_spec.js @@ -57,10 +57,10 @@ describe('Changed file icon', () => { describe.each` file | iconName | tooltipText | desc - ${changedFile()} | ${'file-modified'} | ${'Unstaged modification'} | ${'with file changed'} + ${changedFile()} | ${'file-modified-solid'} | ${'Unstaged modification'} | ${'with file changed'} ${stagedFile()} | ${'file-modified-solid'} | ${'Staged modification'} | ${'with file staged'} - ${changedAndStagedFile()} | ${'file-modified'} | ${'Unstaged and staged modification'} | ${'with file changed and staged'} - ${newFile()} | ${'file-addition'} | ${'Unstaged addition'} | ${'with file new'} + ${changedAndStagedFile()} | ${'file-modified-solid'} | ${'Unstaged and staged modification'} | ${'with file changed and staged'} + ${newFile()} | ${'file-addition-solid'} | ${'Unstaged addition'} | ${'with file new'} `('$desc', ({ file, iconName, tooltipText }) => { beforeEach(() => { factory({ file }); diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index f415fb05b5b..a775c69335e 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -357,10 +357,10 @@ describe MarkupHelper do describe '#markup_unsafe' do subject { helper.markup_unsafe(file_name, text, context) } + let_it_be(:project_base) { create(:project, :repository) } + let_it_be(:context) { { project: project_base } } let(:file_name) { 'foo.bar' } let(:text) { 'Noël' } - let(:project_base) { build(:project, :repository) } - let(:context) { { project: project_base } } context 'when text is missing' do let(:text) { nil } @@ -383,12 +383,21 @@ describe MarkupHelper do context 'when renderer returns an error' do before do - allow(Banzai).to receive(:render).and_raise("An error") + allow(Banzai).to receive(:render).and_raise(StandardError, "An error") end it 'returns html (rendered by ActionView:TextHelper)' do is_expected.to eq('<p>Noël</p>') end + + it 'logs the error' do + expect(Gitlab::ErrorTracking).to receive(:track_exception).with( + instance_of(StandardError), + project_id: project.id, file_name: 'foo.md', context: context + ) + + subject + end end end diff --git a/spec/javascripts/ide/components/commit_sidebar/form_spec.js b/spec/javascripts/ide/components/commit_sidebar/form_spec.js index fdbabf84e25..2ee0b94582c 100644 --- a/spec/javascripts/ide/components/commit_sidebar/form_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/form_spec.js @@ -33,6 +33,12 @@ describe('IDE commit form', () => { }); describe('compact', () => { + beforeEach(done => { + vm.isCompact = true; + + vm.$nextTick(done); + }); + it('renders commit button in compact mode', () => { expect(vm.$el.querySelector('.btn-primary')).not.toBeNull(); expect(vm.$el.querySelector('.btn-primary').textContent).toContain('Commit'); @@ -61,7 +67,7 @@ describe('IDE commit form', () => { }); }); - it('toggles activity bar vie when clicking commit button', done => { + it('toggles activity bar view when clicking commit button', done => { vm.$el.querySelector('.btn-primary').click(); vm.$nextTick(() => { @@ -104,6 +110,17 @@ describe('IDE commit form', () => { }); }); + it('always opens itself in full view current activity view is not commit view when clicking commit button', done => { + vm.$el.querySelector('.btn-primary').click(); + + vm.$nextTick(() => { + expect(store.state.currentActivityView).toBe(activityBarViews.commit); + expect(vm.isCompact).toBe(false); + + done(); + }); + }); + describe('discard draft button', () => { it('hidden when commitMessage is empty', () => { expect(vm.$el.querySelector('.btn-default').textContent).toContain('Collapse'); diff --git a/spec/javascripts/ide/components/repo_tab_spec.js b/spec/javascripts/ide/components/repo_tab_spec.js index 3b52f279bf2..7466ed5468b 100644 --- a/spec/javascripts/ide/components/repo_tab_spec.js +++ b/spec/javascripts/ide/components/repo_tab_spec.js @@ -93,13 +93,13 @@ describe('RepoTab', () => { Vue.nextTick() .then(() => { - expect(vm.$el.querySelector('.file-modified')).toBeNull(); + expect(vm.$el.querySelector('.file-modified-solid')).toBeNull(); vm.$el.dispatchEvent(new Event('mouseout')); }) .then(Vue.nextTick) .then(() => { - expect(vm.$el.querySelector('.file-modified')).not.toBeNull(); + expect(vm.$el.querySelector('.file-modified-solid')).not.toBeNull(); done(); }) diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js index 9c24f20ca9c..d582462d542 100644 --- a/spec/javascripts/ide/stores/actions_spec.js +++ b/spec/javascripts/ide/stores/actions_spec.js @@ -225,6 +225,35 @@ describe('Multi-file store actions', () => { .catch(done.fail); }); + describe('when `gon.feature.stageAllByDefault` is true', () => { + const originalGonFeatures = Object.assign({}, gon.features); + + beforeAll(() => { + gon.features = { stageAllByDefault: true }; + }); + + afterAll(() => { + gon.features = originalGonFeatures; + }); + + it('adds tmp file to staged files', done => { + const name = 'test'; + + store + .dispatch('createTempEntry', { + name, + branchId: 'mybranch', + type: 'blob', + }) + .then(() => { + expect(store.state.stagedFiles).toEqual([jasmine.objectContaining({ name })]); + + done(); + }) + .catch(done.fail); + }); + }); + it('adds tmp file to open files', done => { const name = 'test'; @@ -255,41 +284,25 @@ describe('Multi-file store actions', () => { type: 'blob', }) .then(() => { - const f = store.state.entries[name]; - - expect(store.state.changedFiles.length).toBe(1); - expect(store.state.changedFiles[0].name).toBe(f.name); + expect(store.state.changedFiles).toEqual([ + jasmine.objectContaining({ name, tempFile: true }), + ]); done(); }) .catch(done.fail); }); - it('sets tmp file as active', done => { - testAction( - createTempEntry, - { - name: 'test', - branchId: 'mybranch', - type: 'blob', - }, - store.state, - [ - { type: types.CREATE_TMP_ENTRY, payload: jasmine.any(Object) }, - { type: types.TOGGLE_FILE_OPEN, payload: 'test' }, - { type: types.ADD_FILE_TO_CHANGED, payload: 'test' }, - ], - jasmine.arrayContaining([ - { - type: 'setFileActive', - payload: 'test', - }, - { - type: 'triggerFilesChange', - }, - ]), - done, + it('sets tmp file as active', () => { + const dispatch = jasmine.createSpy(); + const commit = jasmine.createSpy(); + + createTempEntry( + { state: store.state, getters: store.getters, dispatch, commit }, + { name: 'test', branchId: 'mybranch', type: 'blob' }, ); + + expect(dispatch).toHaveBeenCalledWith('setFileActive', 'test'); }); it('creates flash message if file already exists', done => { @@ -800,6 +813,33 @@ describe('Multi-file store actions', () => { }); }); + describe('when `gon.feature.stageAllByDefault` is true', () => { + const originalGonFeatures = Object.assign({}, gon.features); + + beforeAll(() => { + gon.features = { stageAllByDefault: true }; + }); + + afterAll(() => { + gon.features = originalGonFeatures; + }); + + it('by default renames an entry and stages it', () => { + const dispatch = jasmine.createSpy(); + const commit = jasmine.createSpy(); + + renameEntry( + { dispatch, commit, state: store.state, getters: store.getters }, + { path: 'orig', name: 'renamed' }, + ); + + expect(commit.calls.allArgs()).toEqual([ + [types.RENAME_ENTRY, { path: 'orig', name: 'renamed', parentPath: undefined }], + [types.STAGE_CHANGE, jasmine.objectContaining({ path: 'renamed' })], + ]); + }); + }); + it('by default renames an entry and adds to changed', done => { testAction( renameEntry, @@ -819,12 +859,12 @@ describe('Multi-file store actions', () => { payload: 'renamed', }, ], - [{ type: 'burstUnusedSeal' }, { type: 'triggerFilesChange' }], + jasmine.any(Object), done, ); }); - it('if not changed, completely unstages entry if renamed to original', done => { + it('if not changed, completely unstages and discards entry if renamed to original', done => { testAction( renameEntry, { path: 'renamed', name: 'orig' }, diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 9dea74f6345..058afddd73f 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -1735,6 +1735,39 @@ module Gitlab it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:test1 dependencies the build2 should be part of needs') } end + + context 'needs with a Hash type and dependencies with a string type that are mismatching' do + let(:needs) do + [ + "build1", + { job: "build2" } + ] + end + let(:dependencies) { %w(build3) } + + it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:test1 dependencies the build3 should be part of needs') } + end + + context 'needs with an array type and dependency with a string type' do + let(:needs) { %w(build1) } + let(:dependencies) { 'deploy' } + + it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:test1 dependencies should be an array of strings') } + end + + context 'needs with a string type and dependency with an array type' do + let(:needs) { 'build1' } + let(:dependencies) { %w(deploy) } + + it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:test1:needs config can only be a hash or an array') } + end + + context 'needs with a Hash type and dependency with a string type' do + let(:needs) { { job: 'build1' } } + let(:dependencies) { 'deploy' } + + it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:test1 dependencies should be an array of strings') } + end end context 'with when/rules conflict' do diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 7ec655eb113..c2fc228d34a 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -57,7 +57,7 @@ describe Gitlab::Git::Commit, :seed_helper do it { expect(@commit.different_committer?).to be_truthy } it { expect(@commit.parents).to eq(@gitlab_parents) } it { expect(@commit.parent_id).to eq(@parents.first.oid) } - it { expect(@commit.no_commit_message).to eq("--no commit message") } + it { expect(@commit.no_commit_message).to eq("No commit message") } after do # Erase the new commit so other tests get the original repo diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index a9d79454dd5..782d1ac4552 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -277,7 +277,7 @@ describe Commit do describe '#title' do it "returns no_commit_message when safe_message is blank" do allow(commit).to receive(:safe_message).and_return('') - expect(commit.title).to eq("--no commit message") + expect(commit.title).to eq("No commit message") end it 'truncates a message without a newline at natural break to 80 characters' do @@ -308,7 +308,7 @@ eos describe '#full_title' do it "returns no_commit_message when safe_message is blank" do allow(commit).to receive(:safe_message).and_return('') - expect(commit.full_title).to eq("--no commit message") + expect(commit.full_title).to eq("No commit message") end it "returns entire message if there is no newline" do @@ -330,7 +330,7 @@ eos it 'returns no_commit_message when safe_message is blank' do allow(commit).to receive(:safe_message).and_return(nil) - expect(commit.description).to eq('--no commit message') + expect(commit.description).to eq('No commit message') end it 'returns description of commit message if title less than 100 characters' do diff --git a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb index 0e8fe4987b9..f80a3401134 100644 --- a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb +++ b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb @@ -52,8 +52,8 @@ describe 'Mark snippet as spam' do end it 'marks snippet as spam' do - expect_next_instance_of(SpamService) do |instance| - expect(instance).to receive(:mark_as_spam!) + expect_next_instance_of(Spam::MarkAsSpamService) do |instance| + expect(instance).to receive(:execute) end post_graphql_mutation(mutation, current_user: current_user) diff --git a/spec/services/spam/mark_as_spam_service_spec.rb b/spec/services/spam/mark_as_spam_service_spec.rb new file mode 100644 index 00000000000..cba9d6a39cb --- /dev/null +++ b/spec/services/spam/mark_as_spam_service_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Spam::MarkAsSpamService do + let(:user_agent_detail) { build(:user_agent_detail) } + let(:spammable) { build(:issue, user_agent_detail: user_agent_detail) } + let(:fake_akismet_service) { double(:akismet_service, submit_spam: true) } + + subject { described_class.new(spammable: spammable) } + + describe '#execute' do + before do + allow(subject).to receive(:akismet).and_return(fake_akismet_service) + end + + context 'when the spammable object is not submittable' do + before do + allow(spammable).to receive(:submittable_as_spam?).and_return false + end + + it 'does not submit as spam' do + expect(subject.execute).to be_falsey + end + end + + context 'spam is submitted successfully' do + before do + allow(spammable).to receive(:submittable_as_spam?).and_return true + allow(fake_akismet_service).to receive(:submit_spam).and_return true + end + + it 'submits as spam' do + expect(subject.execute).to be_truthy + end + + it "updates the spammable object's user agent detail as being submitted as spam" do + expect(user_agent_detail).to receive(:update_attribute) + + subject.execute + end + + context 'when Akismet does not consider it spam' do + it 'does not update the spammable object as spam' do + allow(fake_akismet_service).to receive(:submit_spam).and_return false + + expect(subject.execute).to be_falsey + end + end + end + end +end |