diff options
Diffstat (limited to 'spec/features/issues')
-rw-r--r-- | spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb | 2 | ||||
-rw-r--r-- | spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb | 5 | ||||
-rw-r--r-- | spec/features/issues/discussion_lock_spec.rb | 108 | ||||
-rw-r--r-- | spec/features/issues/filtered_search/filter_issues_spec.rb | 8 | ||||
-rw-r--r-- | spec/features/issues/gfm_autocomplete_spec.rb | 112 | ||||
-rw-r--r-- | spec/features/issues/issue_header_spec.rb | 164 | ||||
-rw-r--r-- | spec/features/issues/issue_state_spec.rb | 81 | ||||
-rw-r--r-- | spec/features/issues/keyboard_shortcut_spec.rb | 4 | ||||
-rw-r--r-- | spec/features/issues/related_issues_spec.rb | 400 | ||||
-rw-r--r-- | spec/features/issues/service_desk_spec.rb | 26 | ||||
-rw-r--r-- | spec/features/issues/user_edits_issue_spec.rb | 1 | ||||
-rw-r--r-- | spec/features/issues/user_uses_quick_actions_spec.rb | 1 | ||||
-rw-r--r-- | spec/features/issues/user_views_issue_spec.rb | 10 |
13 files changed, 844 insertions, 78 deletions
diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb index 0f0146a26a2..d773126e00c 100644 --- a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb +++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb @@ -105,7 +105,7 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j visit new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid) end - it 'Shows a notice to ask someone else to resolve the threads' do + it 'shows a notice to ask someone else to resolve the threads' do expect(page).to have_content("The threads at #{merge_request.to_reference} will stay unresolved. Ask someone with permission to resolve them.") end end diff --git a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb index b449939a70c..99dc71f0559 100644 --- a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb +++ b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb @@ -31,7 +31,8 @@ RSpec.describe 'Resolve an open thread in a merge request by creating an issue', visit project_merge_request_path(project, merge_request) end - it 'does not show a link to create a new issue' do + # https://gitlab.com/gitlab-org/gitlab/-/issues/285453 + xit 'does not show a link to create a new issue' do expect(page).not_to have_css resolve_discussion_selector end end @@ -81,7 +82,7 @@ RSpec.describe 'Resolve an open thread in a merge request by creating an issue', discussion_to_resolve: discussion.id) end - it 'Shows a notice to ask someone else to resolve the threads' do + it 'shows a notice to ask someone else to resolve the threads' do expect(page).to have_content("The thread at #{merge_request.to_reference}"\ " (discussion #{discussion.first_note.id}) will stay unresolved."\ " Ask someone with permission to resolve it.") diff --git a/spec/features/issues/discussion_lock_spec.rb b/spec/features/issues/discussion_lock_spec.rb new file mode 100644 index 00000000000..13f1742fbf6 --- /dev/null +++ b/spec/features/issues/discussion_lock_spec.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Discussion Lock', :js do + let(:user) { create(:user) } + let(:issue) { create(:issue, project: project, author: user) } + let(:project) { create(:project, :public) } + + before do + sign_in(user) + end + + context 'when a user is a team member' do + before do + project.add_developer(user) + end + + context 'when the discussion is unlocked' do + it 'the user can lock the issue' do + visit project_issue_path(project, issue) + + expect(find('.issuable-sidebar')).to have_content('Unlocked') + + page.within('.issuable-sidebar') do + find('.lock-edit').click + click_button('Lock') + end + + expect(find('#notes')).to have_content('locked this issue') + end + end + + context 'when the discussion is locked' do + before do + issue.update_attribute(:discussion_locked, true) + visit project_issue_path(project, issue) + end + + it 'the user can unlock the issue' do + expect(find('.issuable-sidebar')).to have_content('Locked') + + page.within('.issuable-sidebar') do + find('.lock-edit').click + click_button('Unlock') + end + + expect(find('#notes')).to have_content('unlocked this issue') + expect(find('.issuable-sidebar')).to have_content('Unlocked') + end + + it 'the user can create a comment' do + page.within('#notes .js-main-target-form') do + fill_in 'note[note]', with: 'Some new comment' + click_button 'Comment' + end + + wait_for_requests + + expect(find('div#notes')).to have_content('Some new comment') + end + end + end + + context 'when a user is not a team member' do + context 'when the discussion is unlocked' do + before do + visit project_issue_path(project, issue) + end + + it 'the user can not lock the issue' do + expect(find('.issuable-sidebar')).to have_content('Unlocked') + expect(find('.issuable-sidebar')).not_to have_selector('.lock-edit') + end + + it 'the user can create a comment' do + page.within('#notes .js-main-target-form') do + fill_in 'note[note]', with: 'Some new comment' + click_button 'Comment' + end + + wait_for_requests + + expect(find('div#notes')).to have_content('Some new comment') + end + end + + context 'when the discussion is locked' do + before do + issue.update_attribute(:discussion_locked, true) + visit project_issue_path(project, issue) + end + + it 'the user can not unlock the issue' do + expect(find('.issuable-sidebar')).to have_content('Locked') + expect(find('.issuable-sidebar')).not_to have_selector('.lock-edit') + end + + it 'the user can not create a comment' do + page.within('#notes') do + expect(page).not_to have_selector('js-main-target-form') + expect(page.find('.disabled-comment')) + .to have_content('This issue is locked. Only project members can comment.') + end + end + end + end +end diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index 080943da185..4f4584e7dce 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -153,6 +153,14 @@ RSpec.describe 'Filter issues', :js do end end + describe 'filter by reviewer' do + it 'does not allow filtering by reviewer' do + find('.filtered-search').click + + expect(page).not_to have_button('Reviewer') + end + end + describe 'filter issues by label' do context 'only label' do it 'filters issues by searched label' do diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 06f79f94e8d..07bf821a590 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -418,6 +418,46 @@ RSpec.describe 'GFM autocomplete', :js do end end + context 'when other notes are destroyed' do + let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) } + + # This is meant to protect against this issue https://gitlab.com/gitlab-org/gitlab/-/issues/228729 + it 'keeps autocomplete key listeners' do + visit project_issue_path(project, issue) + note = find('#note-body') + + start_comment_with_emoji(note) + + start_and_cancel_discussion + + note.fill_in(with: '') + start_comment_with_emoji(note) + note.native.send_keys(:enter) + + expect(note.value).to eql('Hello :100: ') + end + + def start_comment_with_emoji(note) + note.native.send_keys('Hello :10') + + wait_for_requests + + find('.atwho-view li', text: '100') + end + + def start_and_cancel_discussion + click_button('Reply...') + + fill_in('note_note', with: 'Whoops!') + + page.accept_alert 'Are you sure you want to cancel creating this comment?' do + click_button('Cancel') + end + + wait_for_requests + end + end + shared_examples 'autocomplete suggestions' do it 'suggests objects correctly' do page.within '.timeline-content-form' do @@ -550,6 +590,15 @@ RSpec.describe 'GFM autocomplete', :js do expect(find('.tribute-container ul', visible: true)).to have_text('alert milestone') end + it 'does not open autocomplete menu when trigger character is prefixed with text' do + page.within '.timeline-content-form' do + find('#note-body').native.send_keys('testing') + find('#note-body').native.send_keys('@') + end + + expect(page).not_to have_selector('.tribute-container', visible: true) + end + it 'selects the first item for assignee dropdowns' do page.within '.timeline-content-form' do find('#note-body').native.send_keys('@') @@ -618,21 +667,6 @@ RSpec.describe 'GFM autocomplete', :js do expect(page).to have_selector('.tribute-container', visible: true) end - it "does not show dropdown when preceded with a special character" do - note = find('#note-body') - page.within '.timeline-content-form' do - note.native.send_keys("@") - end - - expect(page).to have_selector('.tribute-container', visible: true) - - page.within '.timeline-content-form' do - note.native.send_keys("@") - end - - expect(page).not_to have_selector('.tribute-container') - end - it "does not throw an error if no labels exist" do note = find('#note-body') page.within '.timeline-content-form' do @@ -653,14 +687,6 @@ RSpec.describe 'GFM autocomplete', :js do expect_to_wrap(false, user_item, note, user.username) end - it 'doesn\'t open autocomplete after non-word character' do - page.within '.timeline-content-form' do - find('#note-body').native.send_keys("@#{user.username[0..2]}!") - end - - expect(page).not_to have_selector('.tribute-container') - end - it 'triggers autocomplete after selecting a quick action' do note = find('#note-body') page.within '.timeline-content-form' do @@ -848,46 +874,6 @@ RSpec.describe 'GFM autocomplete', :js do it_behaves_like 'autocomplete suggestions' end - - context 'when other notes are destroyed' do - let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) } - - # This is meant to protect against this issue https://gitlab.com/gitlab-org/gitlab/-/issues/228729 - it 'keeps autocomplete key listeners' do - visit project_issue_path(project, issue) - note = find('#note-body') - - start_comment_with_emoji(note) - - start_and_cancel_discussion - - note.fill_in(with: '') - start_comment_with_emoji(note) - note.native.send_keys(:enter) - - expect(note.value).to eql('Hello :100: ') - end - - def start_comment_with_emoji(note) - note.native.send_keys('Hello :10') - - wait_for_requests - - find('.atwho-view li', text: '100') - end - - def start_and_cancel_discussion - click_button('Reply...') - - fill_in('note_note', with: 'Whoops!') - - page.accept_alert 'Are you sure you want to cancel creating this comment?' do - click_button('Cancel') - end - - wait_for_requests - end - end end private diff --git a/spec/features/issues/issue_header_spec.rb b/spec/features/issues/issue_header_spec.rb new file mode 100644 index 00000000000..cf375d8fb67 --- /dev/null +++ b/spec/features/issues/issue_header_spec.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'issue header', :js do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:issue) { create(:issue, project: project) } + let_it_be(:closed_issue) { create(:issue, :closed, project: project) } + let_it_be(:closed_locked_issue) { create(:issue, :closed, :locked, project: project) } + let_it_be(:authored_issue) { create(:issue, project: project, author: user) } + + context 'when user has permission to update' do + before do + project.add_maintainer(user) + sign_in(user) + end + + context 'within the issue actions dropdown menu' do + before do + visit project_issue_path(project, issue) + + # Click on the ellipsis icon + click_button 'Issue actions' + end + + it 'only shows the "New issue" and "Report abuse" items', :aggregate_failures do + expect(page).to have_link 'New issue' + expect(page).to have_link 'Report abuse' + expect(page).not_to have_link 'Submit as spam' + end + end + + context 'when the issue is open' do + before do + visit project_issue_path(project, issue) + end + + it 'has a "Close issue" button' do + expect(page).to have_button 'Close issue' + end + end + + context 'when the issue is closed' do + before do + visit project_issue_path(project, closed_issue) + end + + it 'has a "Reopen issue" button' do + expect(page).to have_button 'Reopen issue' + end + end + + context 'when the issue is closed and locked' do + before do + visit project_issue_path(project, closed_locked_issue) + end + + it 'does not have a "Reopen issue" button' do + expect(page).not_to have_button 'Reopen issue' + end + end + + context 'when the current user is the issue author' do + before do + visit project_issue_path(project, authored_issue) + end + + it 'does not show "Report abuse" link in dropdown' do + click_button 'Issue actions' + + expect(page).not_to have_link 'Report abuse' + end + end + end + + context 'when user is admin and the project is set up for spam' do + let_it_be(:admin) { create(:admin) } + let_it_be(:user_agent_detail) { create(:user_agent_detail, subject: issue) } + + before do + stub_application_setting(akismet_enabled: true) + project.add_maintainer(admin) + sign_in(admin) + end + + context 'within the issue actions dropdown menu' do + before do + visit project_issue_path(project, issue) + + # Click on the ellipsis icon + click_button 'Issue actions' + end + + it 'has "Submit as spam" item' do + expect(page).to have_link 'Submit as spam' + end + end + end + + context 'when user does not have permission to update' do + before do + project.add_guest(user) + sign_in(user) + end + + context 'within the issue actions dropdown menu' do + before do + visit project_issue_path(project, issue) + + # Click on the ellipsis icon + click_button 'Issue actions' + end + + it 'only shows the "New issue" and "Report abuse" items', :aggregate_failures do + expect(page).to have_link 'New issue' + expect(page).to have_link 'Report abuse' + expect(page).not_to have_link 'Submit as spam' + end + end + + context 'when the issue is open' do + before do + visit project_issue_path(project, issue) + end + + it 'does not have a "Close issue" button' do + expect(page).not_to have_button 'Close issue' + end + end + + context 'when the issue is closed' do + before do + visit project_issue_path(project, closed_issue) + end + + it 'does not have a "Reopen issue" button' do + expect(page).not_to have_button 'Reopen issue' + end + end + + context 'when the issue is closed and locked' do + before do + visit project_issue_path(project, closed_locked_issue) + end + + it 'does not have a "Reopen issue" button' do + expect(page).not_to have_button 'Reopen issue' + end + end + + context 'when the current user is the issue author' do + before do + visit project_issue_path(project, authored_issue) + end + + it 'does not show "Report abuse" link in dropdown' do + click_button 'Issue actions' + + expect(page).not_to have_link 'Report abuse' + end + end + end +end diff --git a/spec/features/issues/issue_state_spec.rb b/spec/features/issues/issue_state_spec.rb new file mode 100644 index 00000000000..0ef6eb56dff --- /dev/null +++ b/spec/features/issues/issue_state_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'issue state', :js do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + + before do + project.add_developer(user) + sign_in(user) + end + + shared_examples 'issue closed' do |selector| + it 'can close an issue' do + expect(find('.status-box')).to have_content 'Open' + + within selector do + click_button 'Close issue' + end + + expect(find('.status-box')).to have_content 'Closed' + end + end + + shared_examples 'issue reopened' do |selector| + it 'can reopen an issue' do + expect(find('.status-box')).to have_content 'Closed' + + within selector do + click_button 'Reopen issue' + end + + expect(find('.status-box')).to have_content 'Open' + end + end + + describe 'when open' do + context 'when clicking the top `Close issue` button', :aggregate_failures do + let(:open_issue) { create(:issue, project: project) } + + before do + visit project_issue_path(project, open_issue) + end + + it_behaves_like 'issue closed', '.detail-page-header' + end + + context 'when clicking the bottom `Close issue` button', :aggregate_failures do + let(:open_issue) { create(:issue, project: project) } + + before do + visit project_issue_path(project, open_issue) + end + + it_behaves_like 'issue closed', '.timeline-content-form' + end + end + + describe 'when closed' do + context 'when clicking the top `Reopen issue` button', :aggregate_failures do + let(:closed_issue) { create(:issue, project: project, state: 'closed') } + + before do + visit project_issue_path(project, closed_issue) + end + + it_behaves_like 'issue reopened', '.detail-page-header' + end + + context 'when clicking the bottom `Reopen issue` button', :aggregate_failures do + let(:closed_issue) { create(:issue, project: project, state: 'closed') } + + before do + visit project_issue_path(project, closed_issue) + end + + it_behaves_like 'issue reopened', '.timeline-content-form' + end + end +end diff --git a/spec/features/issues/keyboard_shortcut_spec.rb b/spec/features/issues/keyboard_shortcut_spec.rb index ab40f124257..502412bab5d 100644 --- a/spec/features/issues/keyboard_shortcut_spec.rb +++ b/spec/features/issues/keyboard_shortcut_spec.rb @@ -8,7 +8,7 @@ RSpec.describe 'Issues shortcut', :js do let(:project) { create(:project) } before do - sign_in(create(:admin)) + sign_in(project.owner) visit project_path(project) end @@ -23,7 +23,7 @@ RSpec.describe 'Issues shortcut', :js do let(:project) { create(:project, :issues_disabled) } before do - sign_in(create(:admin)) + sign_in(project.owner) visit project_path(project) end diff --git a/spec/features/issues/related_issues_spec.rb b/spec/features/issues/related_issues_spec.rb new file mode 100644 index 00000000000..837859bbe26 --- /dev/null +++ b/spec/features/issues/related_issues_spec.rb @@ -0,0 +1,400 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Related issues', :js do + let(:user) { create(:user) } + let(:project) { create(:project_empty_repo, :public) } + let(:project_b) { create(:project_empty_repo, :public) } + let(:project_unauthorized) { create(:project_empty_repo, :public) } + let(:issue_a) { create(:issue, project: project) } + let(:issue_b) { create(:issue, project: project) } + let(:issue_c) { create(:issue, project: project) } + let(:issue_d) { create(:issue, project: project) } + let(:issue_project_b_a) { create(:issue, project: project_b) } + let(:issue_project_unauthorized_a) { create(:issue, project: project_unauthorized) } + + context 'widget visibility' do + context 'when not logged in' do + it 'does not show widget when internal project' do + project = create :project_empty_repo, :internal + issue = create :issue, project: project + + visit project_issue_path(project, issue) + + expect(page).not_to have_css('.related-issues-block') + end + + it 'does not show widget when private project' do + project = create :project_empty_repo, :private + issue = create :issue, project: project + + visit project_issue_path(project, issue) + + expect(page).not_to have_css('.related-issues-block') + end + + it 'shows widget when public project' do + project = create :project_empty_repo, :public + issue = create :issue, project: project + + visit project_issue_path(project, issue) + + expect(page).to have_css('.related-issues-block') + expect(page).not_to have_selector('.js-issue-count-badge-add-button') + end + end + + context 'when logged in but not a member' do + before do + gitlab_sign_in(user) + end + + it 'shows widget when internal project' do + project = create :project_empty_repo, :internal + issue = create :issue, project: project + + visit project_issue_path(project, issue) + + expect(page).to have_css('.related-issues-block') + expect(page).not_to have_selector('.js-issue-count-badge-add-button') + end + + it 'does not show widget when private project' do + project = create :project_empty_repo, :private + issue = create :issue, project: project + + visit project_issue_path(project, issue) + + expect(page).not_to have_css('.related-issues-block') + end + + it 'shows widget when public project' do + project = create :project_empty_repo, :public + issue = create :issue, project: project + + visit project_issue_path(project, issue) + + expect(page).to have_css('.related-issues-block') + expect(page).not_to have_selector('.js-issue-count-badge-add-button') + end + + it 'shows widget on their own public issue' do + project = create :project_empty_repo, :public + issue = create :issue, project: project, author: user + + visit project_issue_path(project, issue) + + expect(page).to have_css('.related-issues-block') + expect(page).not_to have_selector('.js-issue-count-badge-add-button') + end + end + + context 'when logged in and a guest' do + before do + gitlab_sign_in(user) + end + + it 'shows widget when internal project' do + project = create :project_empty_repo, :internal + issue = create :issue, project: project + project.add_guest(user) + + visit project_issue_path(project, issue) + + expect(page).to have_css('.related-issues-block') + expect(page).not_to have_selector('.js-issue-count-badge-add-button') + end + + it 'shows widget when private project' do + project = create :project_empty_repo, :private + issue = create :issue, project: project + project.add_guest(user) + + visit project_issue_path(project, issue) + + expect(page).to have_css('.related-issues-block') + expect(page).not_to have_selector('.js-issue-count-badge-add-button') + end + + it 'shows widget when public project' do + project = create :project_empty_repo, :public + issue = create :issue, project: project + project.add_guest(user) + + visit project_issue_path(project, issue) + + expect(page).to have_css('.related-issues-block') + expect(page).not_to have_selector('.js-issue-count-badge-add-button') + end + end + + context 'when logged in and a reporter' do + before do + gitlab_sign_in(user) + end + + it 'shows widget when internal project' do + project = create :project_empty_repo, :internal + issue = create :issue, project: project + project.add_reporter(user) + + visit project_issue_path(project, issue) + + expect(page).to have_css('.related-issues-block') + expect(page).to have_selector('.js-issue-count-badge-add-button') + end + + it 'shows widget when private project' do + project = create :project_empty_repo, :private + issue = create :issue, project: project + project.add_reporter(user) + + visit project_issue_path(project, issue) + + expect(page).to have_css('.related-issues-block') + expect(page).to have_selector('.js-issue-count-badge-add-button') + end + + it 'shows widget when public project' do + project = create :project_empty_repo, :public + issue = create :issue, project: project + project.add_reporter(user) + + visit project_issue_path(project, issue) + + expect(page).to have_css('.related-issues-block') + expect(page).to have_selector('.js-issue-count-badge-add-button') + end + + it 'shows widget on their own public issue' do + project = create :project_empty_repo, :public + issue = create :issue, project: project, author: user + project.add_reporter(user) + + visit project_issue_path(project, issue) + + expect(page).to have_css('.related-issues-block') + expect(page).to have_selector('.js-issue-count-badge-add-button') + end + end + end + + context 'when user has no permission to manage related issues' do + let!(:issue_link_b) { create :issue_link, source: issue_a, target: issue_b } + let!(:issue_link_c) { create :issue_link, source: issue_a, target: issue_c } + + before do + project.add_guest(user) + gitlab_sign_in(user) + end + + context 'visiting some issue someone else created' do + before do + visit project_issue_path(project, issue_a) + wait_for_requests + end + + it 'shows related issues count' do + expect(find('.js-related-issues-header-issue-count')).to have_content('2') + end + end + + context 'visiting issue_b which was targeted by issue_a' do + before do + visit project_issue_path(project, issue_b) + wait_for_requests + end + + it 'shows related issues count' do + expect(find('.js-related-issues-header-issue-count')).to have_content('1') + end + end + end + + context 'when user has permission to manage related issues' do + before do + project.add_maintainer(user) + project_b.add_maintainer(user) + gitlab_sign_in(user) + end + + context 'without existing related issues' do + before do + visit project_issue_path(project, issue_a) + wait_for_requests + end + + it 'shows related issues count' do + expect(find('.js-related-issues-header-issue-count')).to have_content('0') + end + + it 'add related issue' do + find('.js-issue-count-badge-add-button').click + find('.js-add-issuable-form-input').set "#{issue_b.to_reference(project)} " + find('.js-add-issuable-form-add-button').click + + wait_for_requests + + items = all('.item-title a') + + # Form gets hidden after submission + expect(page).not_to have_selector('.js-add-related-issues-form-area') + # Check if related issues are present + expect(items.count).to eq(1) + expect(items[0].text).to eq(issue_b.title) + expect(find('.js-related-issues-header-issue-count')).to have_content('1') + end + + it 'add cross-project related issue' do + find('.js-issue-count-badge-add-button').click + find('.js-add-issuable-form-input').set "#{issue_project_b_a.to_reference(project)} " + find('.js-add-issuable-form-add-button').click + + wait_for_requests + + items = all('.item-title a') + + expect(items.count).to eq(1) + expect(items[0].text).to eq(issue_project_b_a.title) + expect(find('.js-related-issues-header-issue-count')).to have_content('1') + end + + it 'pressing enter should submit the form' do + find('.js-issue-count-badge-add-button').click + find('.js-add-issuable-form-input').set "#{issue_project_b_a.to_reference(project)} " + find('.js-add-issuable-form-input').native.send_key(:enter) + + wait_for_requests + + items = all('.item-title a') + + expect(items.count).to eq(1) + expect(items[0].text).to eq(issue_project_b_a.title) + expect(find('.js-related-issues-header-issue-count')).to have_content('1') + end + + it 'disallows duplicate entries' do + find('.js-issue-count-badge-add-button').click + find('.js-add-issuable-form-input').set 'duplicate duplicate duplicate' + + items = all('.js-add-issuable-form-token-list-item') + expect(items.count).to eq(1) + expect(items[0].text).to eq('duplicate') + + # Pending issues aren't counted towards the related issue count + expect(find('.js-related-issues-header-issue-count')).to have_content('0') + end + + it 'allows us to remove pending issues' do + # Tests against https://gitlab.com/gitlab-org/gitlab/issues/11625 + find('.js-issue-count-badge-add-button').click + find('.js-add-issuable-form-input').set 'issue1 issue2 issue3 ' + + items = all('.js-add-issuable-form-token-list-item') + expect(items.count).to eq(3) + expect(items[0].text).to eq('issue1') + expect(items[1].text).to eq('issue2') + expect(items[2].text).to eq('issue3') + + # Remove pending issues left to right to make sure none get stuck + items[0].find('.js-issue-token-remove-button').click + items = all('.js-add-issuable-form-token-list-item') + expect(items.count).to eq(2) + expect(items[0].text).to eq('issue2') + expect(items[1].text).to eq('issue3') + + items[0].find('.js-issue-token-remove-button').click + items = all('.js-add-issuable-form-token-list-item') + expect(items.count).to eq(1) + expect(items[0].text).to eq('issue3') + + items[0].find('.js-issue-token-remove-button').click + items = all('.js-add-issuable-form-token-list-item') + expect(items.count).to eq(0) + end + end + + context 'with existing related issues' do + let!(:issue_link_b) { create :issue_link, source: issue_a, target: issue_b } + let!(:issue_link_c) { create :issue_link, source: issue_a, target: issue_c } + + before do + visit project_issue_path(project, issue_a) + wait_for_requests + end + + it 'shows related issues count' do + expect(find('.js-related-issues-header-issue-count')).to have_content('2') + end + + it 'shows related issues' do + items = all('.item-title a') + + expect(items.count).to eq(2) + expect(items[0].text).to eq(issue_b.title) + expect(items[1].text).to eq(issue_c.title) + end + + it 'allows us to remove a related issues' do + items_before = all('.item-title a') + + expect(items_before.count).to eq(2) + + first('.js-issue-item-remove-button').click + + wait_for_requests + + items_after = all('.item-title a') + + expect(items_after.count).to eq(1) + end + + it 'add related issue' do + find('.js-issue-count-badge-add-button').click + find('.js-add-issuable-form-input').set "##{issue_d.iid} " + find('.js-add-issuable-form-add-button').click + + wait_for_requests + + items = all('.item-title a') + + expect(items.count).to eq(3) + expect(items[0].text).to eq(issue_b.title) + expect(items[1].text).to eq(issue_c.title) + expect(items[2].text).to eq(issue_d.title) + expect(find('.js-related-issues-header-issue-count')).to have_content('3') + end + + it 'add invalid related issue' do + find('.js-issue-count-badge-add-button').click + find('.js-add-issuable-form-input').set "#9999999 " + find('.js-add-issuable-form-add-button').click + + wait_for_requests + + items = all('.item-title a') + + expect(items.count).to eq(2) + expect(items[0].text).to eq(issue_b.title) + expect(items[1].text).to eq(issue_c.title) + expect(find('.js-related-issues-header-issue-count')).to have_content('2') + end + + it 'add unauthorized related issue' do + find('.js-issue-count-badge-add-button').click + find('.js-add-issuable-form-input').set "#{issue_project_unauthorized_a.to_reference(project)} " + find('.js-add-issuable-form-add-button').click + + wait_for_requests + + items = all('.item-title a') + + expect(items.count).to eq(2) + expect(items[0].text).to eq(issue_b.title) + expect(items[1].text).to eq(issue_c.title) + expect(find('.js-related-issues-header-issue-count')).to have_content('2') + end + end + end +end diff --git a/spec/features/issues/service_desk_spec.rb b/spec/features/issues/service_desk_spec.rb index 1512d539dec..02804d84a21 100644 --- a/spec/features/issues/service_desk_spec.rb +++ b/spec/features/issues/service_desk_spec.rb @@ -4,7 +4,9 @@ require 'spec_helper' RSpec.describe 'Service Desk Issue Tracker', :js do let(:project) { create(:project, :private, service_desk_enabled: true) } - let(:user) { create(:user) } + + let_it_be(:user) { create(:user) } + let_it_be(:support_bot) { User.support_bot } before do # The following two conditions equate to Gitlab::ServiceDesk.supported == true @@ -27,6 +29,16 @@ RSpec.describe 'Service Desk Issue Tracker', :js do end end + context 'issue page' do + let(:service_desk_issue) { create(:issue, project: project, author: support_bot, service_desk_reply_to: 'service.desk@example.com') } + + it 'shows service_desk_reply_to in issue header' do + visit project_issue_path(project, service_desk_issue) + + expect(page).to have_text('by service.desk@example.com via GitLab Support Bot') + end + end + describe 'issues list' do context 'when service desk is supported' do context 'when there are no issues' do @@ -66,10 +78,10 @@ RSpec.describe 'Service Desk Issue Tracker', :js do end context 'when there are issues' do - let(:support_bot) { User.support_bot } - let(:other_user) { create(:user) } - let!(:service_desk_issue) { create(:issue, project: project, author: support_bot) } - let!(:other_user_issue) { create(:issue, project: project, author: other_user) } + let_it_be(:project) { create(:project, :private, service_desk_enabled: true) } + let_it_be(:other_user) { create(:user) } + let_it_be(:service_desk_issue) { create(:issue, project: project, author: support_bot, service_desk_reply_to: 'service.desk@example.com') } + let_it_be(:other_user_issue) { create(:issue, project: project, author: other_user) } describe 'service desk info content' do before do @@ -94,6 +106,10 @@ RSpec.describe 'Service Desk Issue Tracker', :js do it 'only displays issues created by support bot' do expect(page).to have_selector('.issues-list .issue', count: 1) end + + it 'shows service_desk_reply_to in issues list' do + expect(page).to have_text('by service.desk@example.com via GitLab Support Bot') + end end describe 'search box' do diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb index 11b905735de..9d4a6cdb522 100644 --- a/spec/features/issues/user_edits_issue_spec.rb +++ b/spec/features/issues/user_edits_issue_spec.rb @@ -22,6 +22,7 @@ RSpec.describe "Issues > User edits issue", :js do context "from edit page" do before do + stub_licensed_features(multiple_issue_assignees: false) visit edit_project_issue_path(project, issue) end diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb index c5eb3f415ff..d88b816b186 100644 --- a/spec/features/issues/user_uses_quick_actions_spec.rb +++ b/spec/features/issues/user_uses_quick_actions_spec.rb @@ -43,5 +43,6 @@ RSpec.describe 'Issues > User uses quick actions', :js do it_behaves_like 'create_merge_request quick action' it_behaves_like 'move quick action' it_behaves_like 'zoom quick actions' + it_behaves_like 'clone quick action' end end diff --git a/spec/features/issues/user_views_issue_spec.rb b/spec/features/issues/user_views_issue_spec.rb index 4128f3478bb..8792d76981f 100644 --- a/spec/features/issues/user_views_issue_spec.rb +++ b/spec/features/issues/user_views_issue_spec.rb @@ -13,8 +13,6 @@ RSpec.describe "User views issue" do end before do - stub_feature_flags(vue_issue_header: false) - sign_in(user) visit(project_issue_path(project, issue)) @@ -24,10 +22,12 @@ RSpec.describe "User views issue" do it_behaves_like 'page meta description', ' Description header Lorem ipsum dolor sit amet' - it 'shows the merge request and issue actions', :aggregate_failures do - expect(page).to have_link('New issue') + it 'shows the merge request and issue actions', :js, :aggregate_failures do + click_button 'Issue actions' + + expect(page).to have_link('New issue', href: new_project_issue_path(project)) expect(page).to have_button('Create merge request') - expect(page).to have_link('Close issue') + expect(page).to have_button('Close issue') end context 'when the project is archived' do |