diff options
Diffstat (limited to 'spec/features/issues')
20 files changed, 860 insertions, 723 deletions
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb index aa61aff3b05..80bf964e2ee 100644 --- a/spec/features/issues/bulk_assignment_labels_spec.rb +++ b/spec/features/issues/bulk_assignment_labels_spec.rb @@ -295,8 +295,8 @@ RSpec.describe 'Issues > Labels bulk assignment' do before do issue1.milestone = milestone issue2.milestone = milestone - issue1.save - issue2.save + issue1.save! + issue2.save! issue1.labels << bug issue2.labels << feature 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 a4e9df604a9..34d78880991 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 @@ -18,10 +18,6 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j end end - before do - stub_feature_flags(remove_resolve_note: false) - end - describe 'as a user with access to the project' do before do project.add_maintainer(user) @@ -37,7 +33,7 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j context 'resolving the thread' do before do - click_button 'Resolve thread' + find('button[data-qa-selector="resolve_discussion_button"]').click end it 'hides the link for creating a new issue' do 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 99dc71f0559..ac3471e8401 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 @@ -14,10 +14,6 @@ RSpec.describe 'Resolve an open thread in a merge request by creating an issue', "a[title=\"#{title}\"][href=\"#{url}\"]" end - before do - stub_feature_flags(remove_resolve_note: false) - end - describe 'As a user with access to the project' do before do project.add_maintainer(user) @@ -39,7 +35,7 @@ RSpec.describe 'Resolve an open thread in a merge request by creating an issue', context 'resolving the thread' do before do - click_button 'Resolve thread' + find('button[data-qa-selector="resolve_discussion_button"]').click end it 'hides the link for creating a new issue' do diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb index dac066856c0..5ca20028485 100644 --- a/spec/features/issues/form_spec.rb +++ b/spec/features/issues/form_spec.rb @@ -156,7 +156,7 @@ RSpec.describe 'New/edit issue', :js do expect(page.all('input[name="issue[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s) expect(page.all('input[name="issue[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s) - click_button 'Submit issue' + click_button 'Create issue' page.within '.issuable-sidebar' do page.within '.assignee' do diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index e6ebc37ba59..0cefbae4d37 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -3,16 +3,23 @@ require 'spec_helper' RSpec.describe 'GFM autocomplete', :js do - let_it_be(:user_xss_title) { 'eve <img src=x onerror=alert(2)<img src=x onerror=alert(1)>' } - let_it_be(:user_xss) { create(:user, name: user_xss_title, username: 'xss.user') } let_it_be(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') } let_it_be(:user2) { create(:user, name: 'Marge Simpson', username: 'msimpson') } + let_it_be(:group) { create(:group, name: 'Ancestor') } let_it_be(:child_group) { create(:group, parent: group, name: 'My group') } let_it_be(:project) { create(:project, group: child_group) } + + let_it_be(:issue) { create(:issue, project: project, assignees: [user]) } let_it_be(:label) { create(:label, project: project, title: 'special+') } + let_it_be(:label_scoped) { create(:label, project: project, title: 'scoped::label') } + let_it_be(:label_with_spaces) { create(:label, project: project, title: 'Accepting merge requests') } + let_it_be(:snippet) { create(:project_snippet, project: project, title: 'code snippet') } - let(:issue) { create(:issue, project: project) } + let_it_be(:user_xss_title) { 'eve <img src=x onerror=alert(2)<img src=x onerror=alert(1)>' } + let_it_be(:user_xss) { create(:user, name: user_xss_title, username: 'xss.user') } + let_it_be(:label_xss_title) { 'alert label <img src=x onerror="alert(\'Hello xss\');" a' } + let_it_be(:label_xss) { create(:label, project: project, title: label_xss_title) } before_all do project.add_maintainer(user) @@ -21,418 +28,366 @@ RSpec.describe 'GFM autocomplete', :js do end describe 'when tribute_autocomplete feature flag is off' do - before do - stub_feature_flags(tribute_autocomplete: false) - - sign_in(user) - visit project_issue_path(project, issue) - - wait_for_requests - end - - it 'updates issue description with GFM reference' do - click_button 'Edit title and description' - - wait_for_requests - - fill_in 'Description', with: "@#{user.name[0...3]}" - - wait_for_requests - - find_highlighted_autocomplete_item.click - - click_button 'Save changes' - - wait_for_requests - - expect(find('.description')).to have_text(user.to_reference) - end - - it 'opens quick action autocomplete when updating description' do - click_button 'Edit title and description' - - fill_in 'Description', with: '/' - - expect(find_autocomplete_menu).to be_visible - end - - it 'opens autocomplete menu when field starts with text' do - fill_in 'Comment', with: '@' - - expect(find_autocomplete_menu).to be_visible - end - - it 'opens autocomplete menu for Issues when field starts with text with item escaping HTML characters' do - issue_xss_title = 'This will execute alert<img src=x onerror=alert(2)<img src=x onerror=alert(1)>' - create(:issue, project: project, title: issue_xss_title) - - fill_in 'Comment', with: '#' - - wait_for_requests + describe 'new issue page' do + before do + stub_feature_flags(tribute_autocomplete: false) - expect(find_autocomplete_menu).to have_text(issue_xss_title) - end + sign_in(user) + visit new_project_issue_path(project) - it 'opens autocomplete menu for Username when field starts with text with item escaping HTML characters' do - fill_in 'Comment', with: '@ev' + wait_for_requests + end - wait_for_requests + it 'allows quick actions' do + fill_in 'Description', with: '/' - expect(find_highlighted_autocomplete_item).to have_text(user_xss.username) + expect(find_autocomplete_menu).to be_visible + end end - it 'opens autocomplete menu for Milestone when field starts with text with item escaping HTML characters' do - milestone_xss_title = 'alert milestone <img src=x onerror="alert(\'Hello xss\');" a' - create(:milestone, project: project, title: milestone_xss_title) + describe 'issue description' do + let(:issue_to_edit) { create(:issue, project: project) } - fill_in 'Comment', with: '%' + before do + stub_feature_flags(tribute_autocomplete: false) - wait_for_requests + sign_in(user) + visit project_issue_path(project, issue_to_edit) - expect(find_autocomplete_menu).to have_text('alert milestone') - end + wait_for_requests + end - it 'doesnt open autocomplete menu character is prefixed with text' do - fill_in 'Comment', with: 'testing@' + it 'updates with GFM reference' do + click_button 'Edit title and description' - expect(page).not_to have_css('.atwho-view') - end + wait_for_requests - it 'doesnt select the first item for non-assignee dropdowns' do - fill_in 'Comment', with: ':' + fill_in 'Description', with: "@#{user.name[0...3]}" - wait_for_requests + wait_for_requests - expect(find_autocomplete_menu).not_to have_css('.cur') - end + find_highlighted_autocomplete_item.click - it 'does not open autocomplete menu when ":" is prefixed by a number and letters' do - # Number. - fill_in 'Comment', with: '7:' - expect(page).not_to have_css('.atwho-view') + click_button 'Save changes' - # ASCII letter. - fill_in 'Comment', with: 'w:' - expect(page).not_to have_css('.atwho-view') + wait_for_requests - # Non-ASCII letter. - fill_in 'Comment', with: 'Ё:' - expect(page).not_to have_css('.atwho-view') - end + expect(find('.description')).to have_text(user.to_reference) + end - it 'selects the first item for assignee dropdowns' do - fill_in 'Comment', with: '@' + it 'allows quick actions' do + click_button 'Edit title and description' - wait_for_requests + fill_in 'Description', with: '/' - expect(find_autocomplete_menu).to have_css('.cur:first-of-type') + expect(find_autocomplete_menu).to be_visible + end end - it 'includes items for assignee dropdowns with non-ASCII characters in name' do - fill_in 'Comment', with: "@#{user.name[0...8]}" + describe 'issue comment' do + before do + stub_feature_flags(tribute_autocomplete: false) - wait_for_requests + sign_in(user) + visit project_issue_path(project, issue) - expect(find_autocomplete_menu).to have_text(user.name) - end + wait_for_requests + end - it 'searches across full name for assignees' do - fill_in 'Comment', with: '@speciąlsome' + describe 'triggering autocomplete' do + it 'only opens autocomplete menu when trigger character is after whitespace', :aggregate_failures do + fill_in 'Comment', with: 'testing@' + expect(page).not_to have_css('.atwho-view') - wait_for_requests + fill_in 'Comment', with: '@@' + expect(page).not_to have_css('.atwho-view') - expect(find_highlighted_autocomplete_item).to have_text(user.name) - end + fill_in 'Comment', with: "@#{user.username[0..2]}!" + expect(page).not_to have_css('.atwho-view') - it 'shows names that start with the query as the top result' do - fill_in 'Comment', with: '@mar' + fill_in 'Comment', with: "hello:#{user.username[0..2]}" + expect(page).not_to have_css('.atwho-view') - wait_for_requests + fill_in 'Comment', with: '7:' + expect(page).not_to have_css('.atwho-view') - expect(find_highlighted_autocomplete_item).to have_text(user2.name) - end - - it 'shows usernames that start with the query as the top result' do - fill_in 'Comment', with: '@msi' + fill_in 'Comment', with: 'w:' + expect(page).not_to have_css('.atwho-view') - wait_for_requests + fill_in 'Comment', with: 'Ё:' + expect(page).not_to have_css('.atwho-view') - expect(find_highlighted_autocomplete_item).to have_text(user2.name) - end + fill_in 'Comment', with: "test\n\n@" + expect(find_autocomplete_menu).to be_visible + end + end - # Regression test for https://gitlab.com/gitlab-org/gitlab/-/issues/321925 - it 'shows username when pasting then pressing Enter' do - fill_in 'Comment', with: "@#{user.username}\n" + context 'xss checks' do + it 'opens autocomplete menu for Issues when field starts with text with item escaping HTML characters' do + issue_xss_title = 'This will execute alert<img src=x onerror=alert(2)<img src=x onerror=alert(1)>' + create(:issue, project: project, title: issue_xss_title) - expect(find_field('Comment').value).to have_text "@#{user.username}" - end + fill_in 'Comment', with: '#' - it 'does not show `@undefined` when pressing `@` then Enter' do - fill_in 'Comment', with: "@\n" + wait_for_requests - expect(find_field('Comment').value).to have_text '@' - expect(find_field('Comment').value).not_to have_text '@undefined' - end + expect(find_autocomplete_menu).to have_text(issue_xss_title) + end - it 'selects the first item for non-assignee dropdowns if a query is entered' do - fill_in 'Comment', with: ':1' + it 'opens autocomplete menu for Username when field starts with text with item escaping HTML characters' do + fill_in 'Comment', with: '@ev' - wait_for_requests + wait_for_requests - expect(find_autocomplete_menu).to have_css('.cur:first-of-type') - end + expect(find_highlighted_autocomplete_item).to have_text(user_xss.username) + end - context 'if a selected value has special characters' do - it 'wraps the result in double quotes' do - fill_in 'Comment', with: "~#{label.title[0]}" + it 'opens autocomplete menu for Milestone when field starts with text with item escaping HTML characters' do + milestone_xss_title = 'alert milestone <img src=x onerror="alert(\'Hello xss\');" a' + create(:milestone, project: project, title: milestone_xss_title) - find_highlighted_autocomplete_item.click + fill_in 'Comment', with: '%' - expect(find_field('Comment').value).to have_text("~\"#{label.title}\"") - end + wait_for_requests - it "shows dropdown after a new line" do - fill_in 'Comment', with: "test\n\n@" + expect(find_autocomplete_menu).to have_text('alert milestone') + end - expect(find_autocomplete_menu).to be_visible - end + it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do + fill_in 'Comment', with: '~' - it "does not show dropdown when preceded with a special character" do - fill_in 'Comment', with: '@@' + wait_for_requests - expect(page).not_to have_css('.atwho-view') + expect(find_autocomplete_menu).to have_text('alert label') + end end - it 'doesn\'t wrap for assignee values' do - fill_in 'Comment', with: "@#{user.username[0]}" + describe 'autocomplete highlighting' do + it 'auto-selects the first item when there is a query, and only for assignees with no query', :aggregate_failures do + fill_in 'Comment', with: ':' + wait_for_requests + expect(find_autocomplete_menu).not_to have_css('.cur') - find_highlighted_autocomplete_item.click + fill_in 'Comment', with: ':1' + wait_for_requests + expect(find_autocomplete_menu).to have_css('.cur:first-of-type') - expect(find_field('Comment').value).to have_text("@#{user.username}") + fill_in 'Comment', with: '@' + wait_for_requests + expect(find_autocomplete_menu).to have_css('.cur:first-of-type') + end end - it 'doesn\'t wrap for emoji values' do - fill_in 'Comment', with: ':cartwheel_' - - find_highlighted_autocomplete_item.click + describe 'assignees' do + it 'does not wrap with quotes for assignee values' do + fill_in 'Comment', with: "@#{user.username[0]}" - expect(find_field('Comment').value).to have_text('cartwheel_tone1') - end + find_highlighted_autocomplete_item.click - it 'doesn\'t open autocomplete after non-word character' do - fill_in 'Comment', with: "@#{user.username[0..2]}!" + expect(find_field('Comment').value).to have_text("@#{user.username}") + end - expect(page).not_to have_css('.atwho-view') - end + it 'includes items for assignee dropdowns with non-ASCII characters in name' do + fill_in 'Comment', with: "@#{user.name[0...8]}" - it 'doesn\'t open autocomplete if there is no space before' do - fill_in 'Comment', with: "hello:#{user.username[0..2]}" + wait_for_requests - expect(page).not_to have_css('.atwho-view') - end + expect(find_autocomplete_menu).to have_text(user.name) + end - it 'triggers autocomplete after selecting a quick action' do - fill_in 'Comment', with: '/as' + it 'searches across full name for assignees' do + fill_in 'Comment', with: '@speciąlsome' - find_highlighted_autocomplete_item.click + wait_for_requests - expect(find_autocomplete_menu).to have_text(user.username) - end + expect(find_highlighted_autocomplete_item).to have_text(user.name) + end - it 'does not limit quick actions autocomplete list to 5' do - fill_in 'Comment', with: '/' + it 'shows names that start with the query as the top result' do + fill_in 'Comment', with: '@mar' - expect(find_autocomplete_menu).to have_css('li', minimum: 6) - end - end + wait_for_requests - context 'assignees' do - let(:issue_assignee) { create(:issue, project: project) } - let(:unassigned_user) { create(:user) } + expect(find_highlighted_autocomplete_item).to have_text(user2.name) + end - before do - issue_assignee.update(assignees: [user]) + it 'shows usernames that start with the query as the top result' do + fill_in 'Comment', with: '@msi' - project.add_maintainer(unassigned_user) - end + wait_for_requests - it 'lists users who are currently not assigned to the issue when using /assign' do - visit project_issue_path(project, issue_assignee) + expect(find_highlighted_autocomplete_item).to have_text(user2.name) + end - fill_in 'Comment', with: '/as' + # Regression test for https://gitlab.com/gitlab-org/gitlab/-/issues/321925 + it 'shows username when pasting then pressing Enter' do + fill_in 'Comment', with: "@#{user.username}\n" - find_highlighted_autocomplete_item.click + expect(find_field('Comment').value).to have_text "@#{user.username}" + end - expect(find_autocomplete_menu).not_to have_text(user.username) - expect(find_autocomplete_menu).to have_text(unassigned_user.username) - end + it 'does not show `@undefined` when pressing `@` then Enter' do + fill_in 'Comment', with: "@\n" - it 'shows dropdown on new issue form' do - visit new_project_issue_path(project) + expect(find_field('Comment').value).to have_text '@' + expect(find_field('Comment').value).not_to have_text '@undefined' + end - fill_in 'Description', with: '/ass' + context 'when /assign quick action is selected' do + it 'triggers user autocomplete and lists users who are currently not assigned to the issue' do + fill_in 'Comment', with: '/as' - find_highlighted_autocomplete_item.click + find_highlighted_autocomplete_item.click - expect(find_autocomplete_menu).to have_text(unassigned_user.username) - expect(find_autocomplete_menu).to have_text(user.username) + expect(find_autocomplete_menu).not_to have_text(user.username) + expect(find_autocomplete_menu).to have_text(user2.username) + end + end end - end - - context 'labels' do - it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do - label_xss_title = 'alert label <img src=x onerror="alert(\'Hello xss\');" a' - create(:label, project: project, title: label_xss_title) - fill_in 'Comment', with: '~' - - wait_for_requests + context 'if a selected value has special characters' do + it 'wraps the result in double quotes' do + fill_in 'Comment', with: "~#{label.title[0..2]}" - expect(find_autocomplete_menu).to have_text('alert label') - end + find_highlighted_autocomplete_item.click - it 'allows colons when autocompleting scoped labels' do - create(:label, project: project, title: 'scoped:label') + expect(find_field('Comment').value).to have_text("~\"#{label.title}\"") + end - fill_in 'Comment', with: '~scoped:' + it 'doesn\'t wrap for emoji values' do + fill_in 'Comment', with: ':cartwheel_' - wait_for_requests + find_highlighted_autocomplete_item.click - expect(find_autocomplete_menu).to have_text('scoped:label') + expect(find_field('Comment').value).to have_text('cartwheel_tone1') + end end - it 'allows colons when autocompleting scoped labels with double colons' do - create(:label, project: project, title: 'scoped::label') + context 'quick actions' do + it 'does not limit quick actions autocomplete list to 5' do + fill_in 'Comment', with: '/' - fill_in 'Comment', with: '~scoped::' - - wait_for_requests - - expect(find_autocomplete_menu).to have_text('scoped::label') + expect(find_autocomplete_menu).to have_css('li', minimum: 6) + end end - it 'allows spaces when autocompleting multi-word labels' do - create(:label, project: project, title: 'Accepting merge requests') + context 'labels' do + it 'allows colons when autocompleting scoped labels' do + fill_in 'Comment', with: '~scoped:' - fill_in 'Comment', with: '~Accepting merge' + wait_for_requests - wait_for_requests + expect(find_autocomplete_menu).to have_text('scoped::label') + end - expect(find_autocomplete_menu).to have_text('Accepting merge requests') - end + it 'allows spaces when autocompleting multi-word labels' do + fill_in 'Comment', with: '~Accepting merge' - it 'only autocompletes the latest label' do - create(:label, project: project, title: 'Accepting merge requests') - create(:label, project: project, title: 'Accepting job applicants') + wait_for_requests - fill_in 'Comment', with: '~Accepting merge requests foo bar ~Accepting job' + expect(find_autocomplete_menu).to have_text('Accepting merge requests') + end - wait_for_requests + it 'only autocompletes the last label' do + fill_in 'Comment', with: '~scoped:: foo bar ~Accepting merge' - expect(find_autocomplete_menu).to have_text('Accepting job applicants') - end + wait_for_requests - it 'does not autocomplete labels if no tilde is typed' do - create(:label, project: project, title: 'Accepting merge requests') + expect(find_autocomplete_menu).to have_text('Accepting merge requests') + end - fill_in 'Comment', with: 'Accepting merge' + it 'does not autocomplete labels if no tilde is typed' do + fill_in 'Comment', with: 'Accepting merge' - wait_for_requests + wait_for_requests - expect(page).not_to have_css('.atwho-view') + expect(page).not_to have_css('.atwho-view') + end end - end - context 'when other notes are destroyed' do - let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) } + 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_field('Comment') + # This is meant to protect against this issue https://gitlab.com/gitlab-org/gitlab/-/issues/228729 + it 'keeps autocomplete key listeners' do + note = find_field('Comment') - start_comment_with_emoji(note, '.atwho-view li') + start_comment_with_emoji(note, '.atwho-view li') - start_and_cancel_discussion + start_and_cancel_discussion - note.fill_in(with: '') - start_comment_with_emoji(note, '.atwho-view li') - note.native.send_keys(:enter) + note.fill_in(with: '') + start_comment_with_emoji(note, '.atwho-view li') + note.native.send_keys(:enter) - expect(note.value).to eql('Hello :100: ') + expect(note.value).to eql('Hello :100: ') + end end - end - shared_examples 'autocomplete suggestions' do - it 'suggests objects correctly' do - fill_in 'Comment', with: object.class.reference_prefix + shared_examples 'autocomplete suggestions' do + it 'suggests objects correctly' do + fill_in 'Comment', with: object.class.reference_prefix - find_autocomplete_menu.find('li').click + find_autocomplete_menu.find('li').click - expect(find_field('Comment').value).to have_text(expected_body) + expect(find_field('Comment').value).to have_text(expected_body) + end end - end - context 'issues' do - let(:object) { issue } - let(:expected_body) { object.to_reference } + context 'issues' do + let(:object) { issue } + let(:expected_body) { object.to_reference } - it_behaves_like 'autocomplete suggestions' - end - - context 'merge requests' do - let(:object) { create(:merge_request, source_project: project) } - let(:expected_body) { object.to_reference } - - it_behaves_like 'autocomplete suggestions' - end + it_behaves_like 'autocomplete suggestions' + end - context 'project snippets' do - let!(:object) { create(:project_snippet, project: project, title: 'code snippet') } - let(:expected_body) { object.to_reference } + context 'merge requests' do + let(:object) { create(:merge_request, source_project: project) } + let(:expected_body) { object.to_reference } - it_behaves_like 'autocomplete suggestions' - end + it_behaves_like 'autocomplete suggestions' + end - context 'label' do - let!(:object) { label } - let(:expected_body) { object.title } + context 'project snippets' do + let!(:object) { snippet } + let(:expected_body) { object.to_reference } - it_behaves_like 'autocomplete suggestions' - end + it_behaves_like 'autocomplete suggestions' + end - context 'milestone' do - let_it_be(:milestone_expired) { create(:milestone, project: project, due_date: 5.days.ago) } - let_it_be(:milestone_no_duedate) { create(:milestone, project: project, title: 'Foo - No due date') } - let_it_be(:milestone1) { create(:milestone, project: project, title: 'Milestone-1', due_date: 20.days.from_now) } - let_it_be(:milestone2) { create(:milestone, project: project, title: 'Milestone-2', due_date: 15.days.from_now) } - let_it_be(:milestone3) { create(:milestone, project: project, title: 'Milestone-3', due_date: 10.days.from_now) } + context 'milestone' do + let_it_be(:milestone_expired) { create(:milestone, project: project, due_date: 5.days.ago) } + let_it_be(:milestone_no_duedate) { create(:milestone, project: project, title: 'Foo - No due date') } + let_it_be(:milestone1) { create(:milestone, project: project, title: 'Milestone-1', due_date: 20.days.from_now) } + let_it_be(:milestone2) { create(:milestone, project: project, title: 'Milestone-2', due_date: 15.days.from_now) } + let_it_be(:milestone3) { create(:milestone, project: project, title: 'Milestone-3', due_date: 10.days.from_now) } - before do - fill_in 'Comment', with: '/milestone %' + before do + fill_in 'Comment', with: '/milestone %' - wait_for_requests - end + wait_for_requests + end - it 'shows milestons list in the autocomplete menu' do - page.within(find_autocomplete_menu) do - expect(page).to have_selector('li', count: 5) + it 'shows milestons list in the autocomplete menu' do + page.within(find_autocomplete_menu) do + expect(page).to have_selector('li', count: 5) + end end - end - it 'shows expired milestone at the bottom of the list' do - page.within(find_autocomplete_menu) do - expect(page.find('li:last-child')).to have_content milestone_expired.title + it 'shows expired milestone at the bottom of the list' do + page.within(find_autocomplete_menu) do + expect(page.find('li:last-child')).to have_content milestone_expired.title + end end - end - it 'shows milestone due earliest at the top of the list' do - page.within(find_autocomplete_menu) do - aggregate_failures do - expect(page.all('li')[0]).to have_content milestone3.title - expect(page.all('li')[1]).to have_content milestone2.title - expect(page.all('li')[2]).to have_content milestone1.title - expect(page.all('li')[3]).to have_content milestone_no_duedate.title + it 'shows milestone due earliest at the top of the list' do + page.within(find_autocomplete_menu) do + aggregate_failures do + expect(page.all('li')[0]).to have_content milestone3.title + expect(page.all('li')[1]).to have_content milestone2.title + expect(page.all('li')[2]).to have_content milestone1.title + expect(page.all('li')[3]).to have_content milestone_no_duedate.title + end end end end @@ -440,346 +395,303 @@ RSpec.describe 'GFM autocomplete', :js do end describe 'when tribute_autocomplete feature flag is on' do - before do - stub_feature_flags(tribute_autocomplete: true) - - sign_in(user) - visit project_issue_path(project, issue) - - wait_for_requests - end + describe 'issue description' do + let(:issue_to_edit) { create(:issue, project: project) } - it 'updates issue description with GFM reference' do - click_button 'Edit title and description' - - wait_for_requests - - fill_in 'Description', with: "@#{user.name[0...3]}" - - wait_for_requests + before do + stub_feature_flags(tribute_autocomplete: true) - find_highlighted_tribute_autocomplete_menu.click + sign_in(user) + visit project_issue_path(project, issue_to_edit) - click_button 'Save changes' + wait_for_requests + end - wait_for_requests + it 'updates with GFM reference' do + click_button 'Edit title and description' - expect(find('.description')).to have_text(user.to_reference) - end + wait_for_requests - it 'opens autocomplete menu when field starts with text' do - fill_in 'Comment', with: '@' + fill_in 'Description', with: "@#{user.name[0...3]}" - expect(find_tribute_autocomplete_menu).to be_visible - end + wait_for_requests - it 'opens autocomplete menu for Issues when field starts with text with item escaping HTML characters' do - issue_xss_title = 'This will execute alert<img src=x onerror=alert(2)<img src=x onerror=alert(1)>' - create(:issue, project: project, title: issue_xss_title) + find_highlighted_tribute_autocomplete_menu.click - fill_in 'Comment', with: '#' + click_button 'Save changes' - wait_for_requests + wait_for_requests - expect(find_tribute_autocomplete_menu).to have_text(issue_xss_title) + expect(find('.description')).to have_text(user.to_reference) + end end - it 'opens autocomplete menu for Username when field starts with text with item escaping HTML characters' do - fill_in 'Comment', with: '@ev' - - wait_for_requests - - expect(find_tribute_autocomplete_menu).to have_text(user_xss.username) - end + describe 'issue comment' do + before do + stub_feature_flags(tribute_autocomplete: true) - it 'opens autocomplete menu for Milestone when field starts with text with item escaping HTML characters' do - milestone_xss_title = 'alert milestone <img src=x onerror="alert(\'Hello xss\');" a' - create(:milestone, project: project, title: milestone_xss_title) + sign_in(user) + visit project_issue_path(project, issue) - fill_in 'Comment', with: '%' + wait_for_requests + end - wait_for_requests + describe 'triggering autocomplete' do + it 'only opens autocomplete menu when trigger character is after whitespace', :aggregate_failures do + fill_in 'Comment', with: 'testing@' + expect(page).not_to have_css('.tribute-container') - expect(find_tribute_autocomplete_menu).to have_text('alert milestone') - end + fill_in 'Comment', with: "hello:#{user.username[0..2]}" + expect(page).not_to have_css('.tribute-container') - it 'does not open autocomplete menu when trigger character is prefixed with text' do - fill_in 'Comment', with: 'testing@' + fill_in 'Comment', with: '7:' + expect(page).not_to have_css('.tribute-container') - expect(page).not_to have_css('.tribute-container') - end + fill_in 'Comment', with: 'w:' + expect(page).not_to have_css('.tribute-container') - it 'does not open autocomplete menu when ":" is prefixed by a number and letters' do - # Number. - fill_in 'Comment', with: '7:' - expect(page).not_to have_css('.tribute-container') + fill_in 'Comment', with: 'Ё:' + expect(page).not_to have_css('.tribute-container') - # ASCII letter. - fill_in 'Comment', with: 'w:' - expect(page).not_to have_css('.tribute-container') + fill_in 'Comment', with: "test\n\n@" + expect(find_tribute_autocomplete_menu).to be_visible + end + end - # Non-ASCII letter. - fill_in 'Comment', with: 'Ё:' - expect(page).not_to have_css('.tribute-container') - end + context 'xss checks' do + it 'opens autocomplete menu for Issues when field starts with text with item escaping HTML characters' do + issue_xss_title = 'This will execute alert<img src=x onerror=alert(2)<img src=x onerror=alert(1)>' + create(:issue, project: project, title: issue_xss_title) - it 'selects the first item for assignee dropdowns' do - fill_in 'Comment', with: '@' + fill_in 'Comment', with: '#' - wait_for_requests + wait_for_requests - expect(find_tribute_autocomplete_menu).to have_css('.highlight:first-of-type') - end + expect(find_tribute_autocomplete_menu).to have_text(issue_xss_title) + end - it 'includes items for assignee dropdowns with non-ASCII characters in name' do - fill_in 'Comment', with: "@#{user.name[0...8]}" + it 'opens autocomplete menu for Username when field starts with text with item escaping HTML characters' do + fill_in 'Comment', with: '@ev' - wait_for_requests + wait_for_requests - expect(find_tribute_autocomplete_menu).to have_text(user.name) - end + expect(find_tribute_autocomplete_menu).to have_text(user_xss.username) + end - it 'selects the first item for non-assignee dropdowns if a query is entered' do - fill_in 'Comment', with: ':1' + it 'opens autocomplete menu for Milestone when field starts with text with item escaping HTML characters' do + milestone_xss_title = 'alert milestone <img src=x onerror="alert(\'Hello xss\');" a' + create(:milestone, project: project, title: milestone_xss_title) - wait_for_requests + fill_in 'Comment', with: '%' - expect(find_tribute_autocomplete_menu).to have_css('.highlight:first-of-type') - end + wait_for_requests - context 'when autocompleting for groups' do - it 'shows the group when searching for the name of the group' do - fill_in 'Comment', with: '@mygroup' + expect(find_tribute_autocomplete_menu).to have_text('alert milestone') + end - expect(find_tribute_autocomplete_menu).to have_text('My group') - end + it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do + fill_in 'Comment', with: '~' - it 'does not show the group when searching for the name of the parent of the group' do - fill_in 'Comment', with: '@ancestor' + wait_for_requests - expect(find_tribute_autocomplete_menu).not_to have_text('My group') + expect(find_tribute_autocomplete_menu).to have_text('alert label') + end end - end - context 'if a selected value has special characters' do - it 'wraps the result in double quotes' do - fill_in 'Comment', with: "~#{label.title[0]}" + describe 'autocomplete highlighting' do + it 'auto-selects the first item with query', :aggregate_failures do + fill_in 'Comment', with: ':1' + wait_for_requests + expect(find_tribute_autocomplete_menu).to have_css('.highlight:first-of-type') - find_highlighted_tribute_autocomplete_menu.click - - expect(find_field('Comment').value).to have_text("~\"#{label.title}\"") + fill_in 'Comment', with: '@' + wait_for_requests + expect(find_tribute_autocomplete_menu).to have_css('.highlight:first-of-type') + end end - it "shows dropdown after a new line" do - fill_in 'Comment', with: "test\n\n@" - - expect(find_tribute_autocomplete_menu).to be_visible - end + describe 'assignees' do + it 'does not wrap with quotes for assignee values' do + fill_in 'Comment', with: "@#{user.username[0..2]}" - it 'doesn\'t wrap for assignee values' do - fill_in 'Comment', with: "@#{user.username[0..2]}" + find_highlighted_tribute_autocomplete_menu.click - find_highlighted_tribute_autocomplete_menu.click + expect(find_field('Comment').value).to have_text("@#{user.username}") + end - expect(find_field('Comment').value).to have_text("@#{user.username}") - end + it 'includes items for assignee dropdowns with non-ASCII characters in name' do + fill_in 'Comment', with: "@#{user.name[0...8]}" - it 'does not wrap for emoji values' do - fill_in 'Comment', with: ':cartwheel_' + wait_for_requests - find_highlighted_tribute_autocomplete_menu.click + expect(find_tribute_autocomplete_menu).to have_text(user.name) + end - expect(find_field('Comment').value).to have_text('cartwheel_tone1') - end + context 'when autocompleting for groups' do + it 'shows the group when searching for the name of the group' do + fill_in 'Comment', with: '@mygroup' - it 'does not open autocomplete if there is no space before' do - fill_in 'Comment', with: "hello:#{user.username[0..2]}" + expect(find_tribute_autocomplete_menu).to have_text('My group') + end - expect(page).not_to have_css('.tribute-container') - end + it 'does not show the group when searching for the name of the parent of the group' do + fill_in 'Comment', with: '@ancestor' - it 'autocompletes for quick actions' do - fill_in 'Comment', with: '/as' + expect(find_tribute_autocomplete_menu).not_to have_text('My group') + end + end - find_highlighted_tribute_autocomplete_menu.click + context 'when /assign quick action is selected' do + it 'lists users who are currently not assigned to the issue' do + note = find_field('Comment') + note.native.send_keys('/assign ') + # The `/assign` ajax response might replace the one by `@` below causing a failed test + # so we need to wait for the `/assign` ajax request to finish first + wait_for_requests + note.native.send_keys('@') + wait_for_requests + + expect(find_tribute_autocomplete_menu).not_to have_text(user.username) + expect(find_tribute_autocomplete_menu).to have_text(user2.username) + end - expect(find_field('Comment').value).to have_text('/assign') + it 'lists users who are currently not assigned to the issue when using /assign on the second line' do + note = find_field('Comment') + note.native.send_keys('/assign @user2') + note.native.send_keys(:enter) + note.native.send_keys('/assign ') + # The `/assign` ajax response might replace the one by `@` below causing a failed test + # so we need to wait for the `/assign` ajax request to finish first + wait_for_requests + note.native.send_keys('@') + wait_for_requests + + expect(find_tribute_autocomplete_menu).not_to have_text(user.username) + expect(find_tribute_autocomplete_menu).to have_text(user2.username) + end + end end - end - context 'assignees' do - let(:issue_assignee) { create(:issue, project: project) } - let(:unassigned_user) { create(:user) } + context 'if a selected value has special characters' do + it 'wraps the result in double quotes' do + fill_in 'Comment', with: "~#{label.title[0..2]}" - before do - issue_assignee.update(assignees: [user]) + find_highlighted_tribute_autocomplete_menu.click - project.add_maintainer(unassigned_user) - end + expect(find_field('Comment').value).to have_text("~\"#{label.title}\"") + end - it 'lists users who are currently not assigned to the issue when using /assign' do - visit project_issue_path(project, issue_assignee) + it 'does not wrap for emoji values' do + fill_in 'Comment', with: ':cartwheel_' - note = find_field('Comment') - note.native.send_keys('/assign ') - # The `/assign` ajax response might replace the one by `@` below causing a failed test - # so we need to wait for the `/assign` ajax request to finish first - wait_for_requests - note.native.send_keys('@') - wait_for_requests + find_highlighted_tribute_autocomplete_menu.click - expect(find_tribute_autocomplete_menu).not_to have_text(user.username) - expect(find_tribute_autocomplete_menu).to have_text(unassigned_user.username) + expect(find_field('Comment').value).to have_text('cartwheel_tone1') + end end - it 'lists users who are currently not assigned to the issue when using /assign on the second line' do - visit project_issue_path(project, issue_assignee) + context 'quick actions' do + it 'autocompletes for quick actions' do + fill_in 'Comment', with: '/as' - note = find_field('Comment') - note.native.send_keys('/assign @user2') - note.native.send_keys(:enter) - note.native.send_keys('/assign ') - # The `/assign` ajax response might replace the one by `@` below causing a failed test - # so we need to wait for the `/assign` ajax request to finish first - wait_for_requests - note.native.send_keys('@') - wait_for_requests + find_highlighted_tribute_autocomplete_menu.click - expect(find_tribute_autocomplete_menu).not_to have_text(user.username) - expect(find_tribute_autocomplete_menu).to have_text(unassigned_user.username) + expect(find_field('Comment').value).to have_text('/assign') + end end - end - context 'labels' do - it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do - label_xss_title = 'alert label <img src=x onerror="alert(\'Hello xss\');" a' - create(:label, project: project, title: label_xss_title) + context 'labels' do + it 'allows colons when autocompleting scoped labels' do + fill_in 'Comment', with: '~scoped:' - fill_in 'Comment', with: '~' + wait_for_requests - wait_for_requests + expect(find_tribute_autocomplete_menu).to have_text('scoped::label') + end - expect(find_tribute_autocomplete_menu).to have_text('alert label') - end + it 'autocompletes multi-word labels' do + fill_in 'Comment', with: '~Acceptingmerge' - it 'allows colons when autocompleting scoped labels' do - create(:label, project: project, title: 'scoped:label') + wait_for_requests - fill_in 'Comment', with: '~scoped:' + expect(find_tribute_autocomplete_menu).to have_text('Accepting merge requests') + end - wait_for_requests + it 'only autocompletes the last label' do + fill_in 'Comment', with: '~scoped:: foo bar ~Acceptingmerge' + # Invoke autocompletion + find_field('Comment').native.send_keys(:right) - expect(find_tribute_autocomplete_menu).to have_text('scoped:label') - end + wait_for_requests - it 'allows colons when autocompleting scoped labels with double colons' do - create(:label, project: project, title: 'scoped::label') + expect(find_tribute_autocomplete_menu).to have_text('Accepting merge requests') + end - fill_in 'Comment', with: '~scoped::' + it 'does not autocomplete labels if no tilde is typed' do + fill_in 'Comment', with: 'Accepting' - wait_for_requests + wait_for_requests - expect(find_tribute_autocomplete_menu).to have_text('scoped::label') + expect(page).not_to have_css('.tribute-container') + end end - it 'autocompletes multi-word labels' do - create(:label, project: project, title: 'Accepting merge requests') + context 'when other notes are destroyed' do + let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) } - fill_in 'Comment', with: '~Acceptingmerge' + # This is meant to protect against this issue https://gitlab.com/gitlab-org/gitlab/-/issues/228729 + it 'keeps autocomplete key listeners' do + note = find_field('Comment') - wait_for_requests - - expect(find_tribute_autocomplete_menu).to have_text('Accepting merge requests') - end - - it 'only autocompletes the latest label' do - create(:label, project: project, title: 'documentation') - create(:label, project: project, title: 'feature') + start_comment_with_emoji(note, '.tribute-container li') - fill_in 'Comment', with: '~documentation foo bar ~feat' - # Invoke autocompletion - find_field('Comment').native.send_keys(:right) + start_and_cancel_discussion - wait_for_requests + note.fill_in(with: '') + start_comment_with_emoji(note, '.tribute-container li') + note.native.send_keys(:enter) - expect(find_tribute_autocomplete_menu).to have_text('feature') - expect(find_tribute_autocomplete_menu).not_to have_text('documentation') + expect(note.value).to eql('Hello :100: ') + end end - it 'does not autocomplete labels if no tilde is typed' do - create(:label, project: project, title: 'documentation') + shared_examples 'autocomplete suggestions' do + it 'suggests objects correctly' do + fill_in 'Comment', with: object.class.reference_prefix - fill_in 'Comment', with: 'document' - - wait_for_requests + find_tribute_autocomplete_menu.find('li').click - expect(page).not_to have_css('.tribute-container') + expect(find_field('Comment').value).to have_text(expected_body) + end 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_field('Comment') - - start_comment_with_emoji(note, '.tribute-container li') - - start_and_cancel_discussion - note.fill_in(with: '') - start_comment_with_emoji(note, '.tribute-container li') - note.native.send_keys(:enter) + context 'issues' do + let(:object) { issue } + let(:expected_body) { object.to_reference } - expect(note.value).to eql('Hello :100: ') + it_behaves_like 'autocomplete suggestions' end - end - - shared_examples 'autocomplete suggestions' do - it 'suggests objects correctly' do - fill_in 'Comment', with: object.class.reference_prefix - find_tribute_autocomplete_menu.find('li').click + context 'merge requests' do + let(:object) { create(:merge_request, source_project: project) } + let(:expected_body) { object.to_reference } - expect(find_field('Comment').value).to have_text(expected_body) + it_behaves_like 'autocomplete suggestions' end - end - - context 'issues' do - let(:object) { issue } - let(:expected_body) { object.to_reference } - it_behaves_like 'autocomplete suggestions' - end - - context 'merge requests' do - let(:object) { create(:merge_request, source_project: project) } - let(:expected_body) { object.to_reference } - - it_behaves_like 'autocomplete suggestions' - end + context 'project snippets' do + let!(:object) { snippet } + let(:expected_body) { object.to_reference } - context 'project snippets' do - let!(:object) { create(:project_snippet, project: project, title: 'code snippet') } - let(:expected_body) { object.to_reference } - - it_behaves_like 'autocomplete suggestions' - end - - context 'label' do - let!(:object) { label } - let(:expected_body) { object.title } - - it_behaves_like 'autocomplete suggestions' - end + it_behaves_like 'autocomplete suggestions' + end - context 'milestone' do - let!(:object) { create(:milestone, project: project) } - let(:expected_body) { object.to_reference } + context 'milestone' do + let!(:object) { create(:milestone, project: project) } + let(:expected_body) { object.to_reference } - it_behaves_like 'autocomplete suggestions' + it_behaves_like 'autocomplete suggestions' + end end end diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb index ca44978d223..04b4caa52fe 100644 --- a/spec/features/issues/issue_sidebar_spec.rb +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -30,79 +30,199 @@ RSpec.describe 'Issue Sidebar' do let(:user2) { create(:user) } let(:issue2) { create(:issue, project: project, author: user2) } - include_examples 'issuable invite members experiments' do - let(:issuable_path) { project_issue_path(project, issue2) } - end - - context 'when user is a developer' do + context 'when GraphQL assignees widget feature flag is disabled' do before do - project.add_developer(user) - visit_issue(project, issue2) - - find('.block.assignee .edit-link').click + stub_feature_flags(issue_assignees_widget: false) + end - wait_for_requests + include_examples 'issuable invite members experiments' do + let(:issuable_path) { project_issue_path(project, issue2) } end - it 'shows author in assignee dropdown' do - page.within '.dropdown-menu-user' do - expect(page).to have_content(user2.name) + context 'when user is a developer' do + before do + project.add_developer(user) + visit_issue(project, issue2) + + find('.block.assignee .edit-link').click + wait_for_requests + end + + it 'shows author in assignee dropdown' do + page.within '.dropdown-menu-user' do + expect(page).to have_content(user2.name) + end + end + + it 'shows author when filtering assignee dropdown' do + page.within '.dropdown-menu-user' do + find('.dropdown-input-field').set(user2.name) + + wait_for_requests + + expect(page).to have_content(user2.name) + end + end + + it 'assigns yourself' do + find('.block.assignee .dropdown-menu-toggle').click + + click_button 'assign yourself' + + wait_for_requests + + find('.block.assignee .edit-link').click + + page.within '.dropdown-menu-user' do + expect(page.find('.dropdown-header')).to be_visible + expect(page.find('.dropdown-menu-user-link.is-active')).to have_content(user.name) + end end - end - it 'shows author when filtering assignee dropdown' do - page.within '.dropdown-menu-user' do + it 'keeps your filtered term after filtering and dismissing the dropdown' do find('.dropdown-input-field').set(user2.name) wait_for_requests - expect(page).to have_content(user2.name) + page.within '.dropdown-menu-user' do + expect(page).not_to have_content 'Unassigned' + click_link user2.name + end + + find('.js-right-sidebar').click + find('.block.assignee .edit-link').click + + expect(page.all('.dropdown-menu-user li').length).to eq(1) + expect(find('.dropdown-input-field').value).to eq(user2.name) + end + + it 'shows label text as "Apply" when assignees are changed' do + project.add_developer(user) + visit_issue(project, issue2) + + find('.block.assignee .edit-link').click + wait_for_requests + + click_on 'Unassigned' + + expect(page).to have_link('Apply') end end + end - it 'assigns yourself' do - find('.block.assignee .dropdown-menu-toggle').click + context 'when GraphQL assignees widget feature flag is enabled' do + context 'when a privileged user can invite' do + it 'shows a link for inviting members and launches invite modal' do + project.add_maintainer(user) + visit_issue(project, issue2) - click_button 'assign yourself' + open_assignees_dropdown - wait_for_requests + page.within '.dropdown-menu-user' do + expect(page).to have_link('Invite members') + expect(page).to have_selector('[data-track-event="click_invite_members"]') + expect(page).to have_selector('[data-track-label="edit_assignee"]') + end - find('.block.assignee .edit-link').click + click_link 'Invite members' - page.within '.dropdown-menu-user' do - expect(page.find('.dropdown-header')).to be_visible - expect(page.find('.dropdown-menu-user-link.is-active')).to have_content(user.name) + expect(page).to have_content("You're inviting members to the") end end - it 'keeps your filtered term after filtering and dismissing the dropdown' do - find('.dropdown-input-field').set(user2.name) + context 'when invite_members_version_b experiment is enabled' do + before do + stub_experiment_for_subject(invite_members_version_b: true) + end + + it 'shows a link for inviting members and follows through to modal' do + project.add_developer(user) + visit_issue(project, issue2) - wait_for_requests + open_assignees_dropdown - page.within '.dropdown-menu-user' do - expect(page).not_to have_content 'Unassigned' - click_link user2.name + page.within '.dropdown-menu-user' do + expect(page).to have_link('Invite members', href: '#') + expect(page).to have_selector('[data-track-event="click_invite_members_version_b"]') + expect(page).to have_selector('[data-track-label="edit_assignee"]') + end + + click_link 'Invite members' + + expect(page).to have_content("Oops, this feature isn't ready yet") end + end + + context 'when invite_members_version_b experiment is disabled' do + it 'shows author in assignee dropdown and no invite link' do + project.add_developer(user) + visit_issue(project, issue2) - find('.js-right-sidebar').click - find('.block.assignee .edit-link').click + open_assignees_dropdown - expect(page.all('.dropdown-menu-user li').length).to eq(1) - expect(find('.dropdown-input-field').value).to eq(user2.name) + page.within '.dropdown-menu-user' do + expect(page).not_to have_link('Invite members') + end + end end - end - it 'shows label text as "Apply" when assignees are changed' do - project.add_developer(user) - visit_issue(project, issue2) + context 'when user is a developer' do + before do + project.add_developer(user) + visit_issue(project, issue2) + end + + it 'shows author in assignee dropdown' do + open_assignees_dropdown + + page.within '.dropdown-menu-user' do + expect(page).to have_content(user2.name) + end + end + + it 'shows author when filtering assignee dropdown' do + open_assignees_dropdown + + page.within '.dropdown-menu-user' do + find('.js-dropdown-input-field').find('input').set(user2.name) + + wait_for_requests + + expect(page).to have_content(user2.name) + end + end + + it 'assigns yourself' do + click_button 'assign yourself' + wait_for_requests + + page.within '.assignee' do + expect(page).to have_content(user.name) + end + end - find('.block.assignee .edit-link').click - wait_for_requests + it 'keeps your filtered term after filtering and dismissing the dropdown' do + open_assignees_dropdown - click_on 'Unassigned' + find('.js-dropdown-input-field').find('input').set(user2.name) + wait_for_requests + + page.within '.dropdown-menu-user' do + expect(page).not_to have_content 'Unassigned' + click_link user2.name + end - expect(page).to have_link('Apply') + find('.js-right-sidebar').click + + open_assignees_dropdown + + page.within('.assignee') do + expect(page.all('[data-testid="selected-participant"]').length).to eq(1) + end + + expect(find('.js-dropdown-input-field').find('input').value).to eq(user2.name) + end + end end end @@ -171,7 +291,7 @@ RSpec.describe 'Issue Sidebar' do context 'editing issue labels', :js do before do - issue.update(labels: [label]) + issue.update!(labels: [label]) page.within('.block.labels') do click_on 'Edit' end @@ -334,4 +454,11 @@ RSpec.describe 'Issue Sidebar' do find('aside.right-sidebar.right-sidebar-collapsed .js-sidebar-toggle').click find('aside.right-sidebar.right-sidebar-expanded') end + + def open_assignees_dropdown + page.within('.assignee') do + click_button('Edit') + wait_for_requests + end + end end diff --git a/spec/features/issues/markdown_toolbar_spec.rb b/spec/features/issues/markdown_toolbar_spec.rb index 6dc1cbfb2d7..aad5d319bc4 100644 --- a/spec/features/issues/markdown_toolbar_spec.rb +++ b/spec/features/issues/markdown_toolbar_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' RSpec.describe 'Issue markdown toolbar', :js do - let(:project) { create(:project, :public) } - let(:issue) { create(:issue, project: project) } - let(:user) { create(:user) } + let_it_be(:project) { create(:project, :public) } + let_it_be(:issue) { create(:issue, project: project) } + let_it_be(:user) { create(:user) } before do sign_in(user) @@ -14,28 +14,22 @@ RSpec.describe 'Issue markdown toolbar', :js do end it "doesn't include first new line when adding bold" do - find('#note-body').native.send_keys('test') - find('#note-body').native.send_key(:enter) - find('#note-body').native.send_keys('bold') + fill_in 'Comment', with: "test\nbold" - find('.js-main-target-form #note-body') - page.evaluate_script('document.querySelectorAll(".js-main-target-form #note-body")[0].setSelectionRange(4, 9)') + page.evaluate_script('document.getElementById("note-body").setSelectionRange(4, 9)') - first('.toolbar-btn').click + click_button 'Add bold text' - expect(find('#note-body')[:value]).to eq("test\n**bold**\n") + expect(find_field('Comment').value).to eq("test\n**bold**\n") end it "doesn't include first new line when adding underline" do - find('#note-body').native.send_keys('test') - find('#note-body').native.send_key(:enter) - find('#note-body').native.send_keys('underline') + fill_in 'Comment', with: "test\nunderline" - find('.js-main-target-form #note-body') - page.evaluate_script('document.querySelectorAll(".js-main-target-form #note-body")[0].setSelectionRange(4, 50)') + page.evaluate_script('document.getElementById("note-body").setSelectionRange(4, 50)') - all('.toolbar-btn')[1].click + click_button 'Add italic text' - expect(find('#note-body')[:value]).to eq("test\n_underline_\n") + expect(find_field('Comment').value).to eq("test\n_underline_\n") end end diff --git a/spec/features/issues/note_polling_spec.rb b/spec/features/issues/note_polling_spec.rb index bc4c67fdd79..5e02d5ad038 100644 --- a/spec/features/issues/note_polling_spec.rb +++ b/spec/features/issues/note_polling_spec.rb @@ -103,7 +103,7 @@ RSpec.describe 'Issue notes polling', :js do end def update_note(note, new_text) - note.update(note: new_text) + note.update!(note: new_text) wait_for_requests end diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb index aec806c566d..461030d3176 100644 --- a/spec/features/issues/spam_issues_spec.rb +++ b/spec/features/issues/spam_issues_spec.rb @@ -43,7 +43,7 @@ RSpec.describe 'New issue', :js do end it 'rejects issue creation' do - click_button 'Submit issue' + click_button 'Create issue' expect(page).to have_content('discarded') expect(page).not_to have_content('potential spam') @@ -51,7 +51,7 @@ RSpec.describe 'New issue', :js do end it 'creates a spam log record' do - expect { click_button 'Submit issue' } + expect { click_button 'Create issue' } .to log_spam(title: 'issue title', description: 'issue description', user_id: user.id, noteable_type: 'Issue') end end @@ -63,14 +63,14 @@ RSpec.describe 'New issue', :js do end it 'allows issue creation' do - click_button 'Submit issue' + click_button 'Create issue' expect(page.find('.issue-details h2.title')).to have_content('issue title') expect(page.find('.issue-details .description')).to have_content('issue description') end it 'creates a spam log record' do - expect { click_button 'Submit issue' } + expect { click_button 'Create issue' } .to log_spam(title: 'issue title', description: 'issue description', user_id: user.id, noteable_type: 'Issue') end end @@ -101,14 +101,14 @@ RSpec.describe 'New issue', :js do fill_in 'issue_title', with: 'issue title' fill_in 'issue_description', with: 'issue description' - click_button 'Submit issue' + click_button 'Create issue' # it is impossible to test reCAPTCHA automatically and there is no possibility to fill in recaptcha # reCAPTCHA verification is skipped in test environment and it always returns true expect(page).not_to have_content('issue title') expect(page).to have_css('.recaptcha') - click_button 'Submit issue' + click_button 'Create issue' expect(page.find('.issue-details h2.title')).to have_content('issue title') expect(page.find('.issue-details .description')).to have_content('issue description') @@ -122,7 +122,7 @@ RSpec.describe 'New issue', :js do end it 'creates an issue without a need to solve reCAPTCHA' do - click_button 'Submit issue' + click_button 'Create issue' expect(page).not_to have_css('.recaptcha') expect(page.find('.issue-details h2.title')).to have_content('issue title') @@ -130,7 +130,7 @@ RSpec.describe 'New issue', :js do end it 'creates a spam log record' do - expect { click_button 'Submit issue' } + expect { click_button 'Create issue' } .to log_spam(title: 'issue title', description: 'issue description', user_id: user.id, noteable_type: 'Issue') end end @@ -148,7 +148,7 @@ RSpec.describe 'New issue', :js do end it 'creates an issue without a need to solve reCaptcha' do - click_button 'Submit issue' + click_button 'Create issue' expect(page).not_to have_css('.recaptcha') expect(page.find('.issue-details h2.title')).to have_content('issue title') @@ -156,7 +156,7 @@ RSpec.describe 'New issue', :js do end it 'creates a spam log record' do - expect { click_button 'Submit issue' } + expect { click_button 'Create issue' } .to log_spam(title: 'issue title', description: 'issue description', user_id: user.id, noteable_type: 'Issue') end end @@ -178,7 +178,7 @@ RSpec.describe 'New issue', :js do fill_in 'issue_title', with: 'issue title' fill_in 'issue_description', with: 'issue description' - click_button 'Submit issue' + click_button 'Create issue' expect(page.find('.issue-details h2.title')).to have_content('issue title') expect(page.find('.issue-details .description')).to have_content('issue description') diff --git a/spec/features/issues/user_comments_on_issue_spec.rb b/spec/features/issues/user_comments_on_issue_spec.rb index 004488f2f64..09d3ad15641 100644 --- a/spec/features/issues/user_comments_on_issue_spec.rb +++ b/spec/features/issues/user_comments_on_issue_spec.rb @@ -57,17 +57,9 @@ RSpec.describe "User comments on issue", :js do project.add_maintainer(user) create(:label, project: project, title: 'label') - page.within '.timeline-content-form' do - find('#note-body').native.send_keys('/l') - end - - wait_for_requests - - expect(page).to have_selector('.atwho-container') + fill_in 'Comment', with: '/l' - page.within '.atwho-container #at-view-commands' do - expect(find('li', match: :first)).to have_content('/label') - end + expect(find_highlighted_autocomplete_item).to have_content('/label') end end @@ -110,4 +102,10 @@ RSpec.describe "User comments on issue", :js do end end end + + private + + def find_highlighted_autocomplete_item + find('.atwho-view li.cur', visible: true) + end end diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb index e225a45481d..6e8b3e4fb7c 100644 --- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb +++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb @@ -150,7 +150,7 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do context 'when merge requests are disabled' do before do - project.project_feature.update(merge_requests_access_level: 0) + project.project_feature.update!(merge_requests_access_level: 0) visit project_issue_path(project, issue) end diff --git a/spec/features/issues/user_creates_confidential_merge_request_spec.rb b/spec/features/issues/user_creates_confidential_merge_request_spec.rb index ea96165d7b7..6b4526cd624 100644 --- a/spec/features/issues/user_creates_confidential_merge_request_spec.rb +++ b/spec/features/issues/user_creates_confidential_merge_request_spec.rb @@ -38,7 +38,7 @@ RSpec.describe 'User creates confidential merge request on issue page', :js do let(:forked_project) { fork_project(project, user, repository: true) } before do - forked_project.update(visibility: Gitlab::VisibilityLevel::PRIVATE) + forked_project.update!(visibility: Gitlab::VisibilityLevel::PRIVATE) visit_confidential_issue end diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb index 98f9ed6c6a2..e2e204f03db 100644 --- a/spec/features/issues/user_creates_issue_spec.rb +++ b/spec/features/issues/user_creates_issue_spec.rb @@ -45,7 +45,7 @@ RSpec.describe "User creates issue" do .and have_no_content("Milestone") expect(page.find('#issue_title')['placeholder']).to eq 'Title' - expect(page.find('#issue_description')['placeholder']).to eq 'Write a comment or drag your files here…' + expect(page.find('#issue_description')['placeholder']).to eq 'Write a description or drag your files here…' end issue_title = "500 error on profile" @@ -54,7 +54,7 @@ RSpec.describe "User creates issue" do first('.js-md').click first('.rspec-issuable-form-description').native.send_keys('Description') - click_button("Submit issue") + click_button("Create issue") expect(page).to have_content(issue_title) .and have_content(user.name) @@ -112,7 +112,7 @@ RSpec.describe "User creates issue" do fill_in("Title", with: issue_title) click_button("Label") click_link(label_titles.first) - click_button("Submit issue") + click_button("Create issue") expect(page).to have_content(issue_title) .and have_content(user.name) @@ -135,7 +135,7 @@ RSpec.describe "User creates issue" do expect(find('#issuable-due-date').value).to eq date.to_s - click_button 'Submit issue' + click_button 'Create issue' page.within '.issuable-sidebar' do expect(page).to have_content date.to_s(:medium) @@ -259,7 +259,7 @@ RSpec.describe "User creates issue" do fill_in 'issue_title', with: 'bug 345' fill_in 'issue_description', with: 'bug description' - click_button 'Submit issue' + click_button 'Create issue' end end end diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb index 9d4a6cdb522..1bbb96ff479 100644 --- a/spec/features/issues/user_edits_issue_spec.rb +++ b/spec/features/issues/user_edits_issue_spec.rb @@ -78,7 +78,7 @@ RSpec.describe "Issues > User edits issue", :js do end it 'warns about version conflict' do - issue.update(title: "New title") + issue.update!(title: "New title") fill_in 'issue_title', with: 'bug 345' fill_in 'issue_description', with: 'bug description' @@ -142,10 +142,8 @@ RSpec.describe "Issues > User edits issue", :js do it 'can remove label without removing label added via quick action', :aggregate_failures do # Add `syzygy` label with a quick action - note = find('#note-body') - page.within '.timeline-content-form' do - note.native.send_keys('/label ~syzygy') - end + fill_in 'Comment', with: '/label ~syzygy' + click_button 'Comment' wait_for_requests @@ -169,80 +167,165 @@ RSpec.describe "Issues > User edits issue", :js do end describe 'update assignee' do - context 'by authorized user' do - def close_dropdown_menu_if_visible - find('.dropdown-menu-toggle', visible: :all).tap do |toggle| - toggle.click if toggle.visible? - end + context 'when GraphQL assignees widget feature flag is disabled' do + before do + stub_feature_flags(issue_assignees_widget: false) end - it 'allows user to select unassigned' do - visit project_issue_path(project, issue) + context 'by authorized user' do + def close_dropdown_menu_if_visible + find('.dropdown-menu-toggle', visible: :all).tap do |toggle| + toggle.click if toggle.visible? + end + end - page.within('.assignee') do - expect(page).to have_content "#{user.name}" + it 'allows user to select unassigned' do + visit project_issue_path(project, issue) - click_link 'Edit' - click_link 'Unassigned' - first('.title').click - expect(page).to have_content 'None - assign yourself' + 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 - end - it 'allows user to select an assignee' do - issue2 = create(:issue, project: project, author: user) - visit project_issue_path(project, issue2) + 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" + 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 - page.within '.assignee' do - click_link 'Edit' + it 'allows user to unselect themselves' do + issue2 = create(:issue, project: project, author: user, assignees: [user]) + + visit project_issue_path(project, issue2) + + page.within '.assignee' do + expect(page).to have_content user.name + + 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) } - page.within '.dropdown-menu-user' do - click_link user.name + before do + project.add_guest(guest) end - page.within('.assignee') do - expect(page).to have_content user.name + 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 - it 'allows user to unselect themselves' do - issue2 = create(:issue, project: project, author: user, assignees: [user]) + context 'when GraphQL assignees widget feature flag is enabled' do + context 'by authorized user' do + it 'allows user to select unassigned' do + visit project_issue_path(project, issue) - visit project_issue_path(project, issue2) + page.within('.assignee') do + expect(page).to have_content "#{user.name}" - page.within '.assignee' do - expect(page).to have_content user.name + click_button('Edit') + wait_for_requests - click_link 'Edit' - click_link user.name + find('[data-testid="unassign"]').click + find('[data-testid="title"]').click + wait_for_requests + + expect(page).to have_content 'None - assign yourself' + end + end - close_dropdown_menu_if_visible + it 'allows user to select an assignee' do + issue2 = create(:issue, project: project, author: user) + visit project_issue_path(project, issue2) - page.within '.value .assign-yourself' do + page.within('.assignee') do expect(page).to have_content "None" + click_button('Edit') + wait_for_requests + end + + page.within '.dropdown-menu-user' do + click_link user.name + end + + page.within('.assignee') do + find('[data-testid="title"]').click + wait_for_requests + + expect(page).to have_content user.name end end - end - end - context 'by unauthorized user' do - let(:guest) { create(:user) } + it 'allows user to unselect themselves' do + issue2 = create(:issue, project: project, author: user, assignees: [user]) - before do - project.add_guest(guest) + visit project_issue_path(project, issue2) + + page.within '.assignee' do + expect(page).to have_content user.name + + click_button('Edit') + wait_for_requests + click_link user.name + + find('[data-testid="title"]').click + wait_for_requests + + expect(page).to have_content "None" + end + end end - it 'shows assignee text' do - sign_out(:user) - sign_in(guest) + context 'by unauthorized user' do + let(:guest) { create(:user) } - visit project_issue_path(project, issue) - expect(page).to have_content issue.assignees.first.name + 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 end @@ -309,7 +392,7 @@ RSpec.describe "Issues > User edits issue", :js do before do project.add_guest(guest) issue.milestone = milestone - issue.save + issue.save! end it 'shows milestone text' do @@ -326,24 +409,23 @@ RSpec.describe "Issues > User edits issue", :js 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 '[data-testid="due-date"]' do + click_button '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') + expect(find('[data-testid="sidebar-duedate-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 '[data-testid="due-date"]' do + click_button 'Edit' page.within '.pika-single' do click_button date.day @@ -353,7 +435,7 @@ RSpec.describe "Issues > User edits issue", :js do expect(page).to have_no_content 'None' - click_link 'remove due date' + click_button 'remove due date' expect(page).to have_content 'None' end end diff --git a/spec/features/issues/user_filters_issues_spec.rb b/spec/features/issues/user_filters_issues_spec.rb index 1b246181523..5d05df6aaf0 100644 --- a/spec/features/issues/user_filters_issues_spec.rb +++ b/spec/features/issues/user_filters_issues_spec.rb @@ -18,7 +18,7 @@ RSpec.describe 'User filters issues', :js do @issue = Issue.find_by(title: 'foobar') @issue.milestone = create(:milestone, project: project) @issue.assignees = [] - @issue.save + @issue.save! end let(:issue) { @issue } diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb index 1c7bc5f239f..e862f7030c0 100644 --- a/spec/features/issues/user_interacts_with_awards_spec.rb +++ b/spec/features/issues/user_interacts_with_awards_spec.rb @@ -5,6 +5,10 @@ require 'spec_helper' RSpec.describe 'User interacts with awards' do let(:user) { create(:user) } + before do + stub_feature_flags(improved_emoji_picker: false) + end + describe 'User interacts with awards in an issue', :js do let(:issue) { create(:issue, project: project)} let(:project) { create(:project) } diff --git a/spec/features/issues/user_invites_from_a_comment_spec.rb b/spec/features/issues/user_invites_from_a_comment_spec.rb new file mode 100644 index 00000000000..82061f6ed79 --- /dev/null +++ b/spec/features/issues/user_invites_from_a_comment_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "User invites from a comment", :js do + let_it_be(:project) { create(:project_empty_repo, :public) } + let_it_be(:issue) { create(:issue, project: project) } + let_it_be(:user) { project.owner } + + before do + sign_in(user) + end + + it "launches the invite modal from invite link on a comment" do + stub_experiments(invite_members_in_comment: :invite_member_link) + + visit project_issue_path(project, issue) + + page.within(".new-note") do + click_button 'Invite Member' + end + + expect(page).to have_content("You're inviting members to the") + end +end diff --git a/spec/features/issues/user_sees_live_update_spec.rb b/spec/features/issues/user_sees_live_update_spec.rb index 79c6978cbc0..7e4880f209e 100644 --- a/spec/features/issues/user_sees_live_update_spec.rb +++ b/spec/features/issues/user_sees_live_update_spec.rb @@ -18,7 +18,7 @@ RSpec.describe 'Issues > User sees live update', :js do expect(page).to have_text("new title") - issue.update(title: "updated title") + issue.update!(title: "updated title") wait_for_requests expect(page).to have_text("updated title") diff --git a/spec/features/issues/user_sees_sidebar_updates_in_realtime_spec.rb b/spec/features/issues/user_sees_sidebar_updates_in_realtime_spec.rb index 7a2b637e48e..6473fe01052 100644 --- a/spec/features/issues/user_sees_sidebar_updates_in_realtime_spec.rb +++ b/spec/features/issues/user_sees_sidebar_updates_in_realtime_spec.rb @@ -19,11 +19,14 @@ RSpec.describe 'Issues > Real-time sidebar', :js do expect(page.find('.assignee')).to have_content 'None' end - gitlab_sign_in(user) + sign_in(user) + visit project_issue_path(project, issue) expect(page.find('.assignee')).to have_content 'None' click_button 'assign yourself' + wait_for_requests + expect(page.find('.assignee')).to have_content user.name using_session :other_session do expect(page.find('.assignee')).to have_content user.name diff --git a/spec/features/issues/user_sorts_issues_spec.rb b/spec/features/issues/user_sorts_issues_spec.rb index f0bb055c6f2..c161e1deb83 100644 --- a/spec/features/issues/user_sorts_issues_spec.rb +++ b/spec/features/issues/user_sorts_issues_spec.rb @@ -77,7 +77,7 @@ RSpec.describe "User sorts issues" do it 'sorts by most recently updated', :js do issue3.updated_at = Time.now + 100 - issue3.save + issue3.save! visit project_issues_path(project, sort: sort_value_recently_updated) expect(first_issue).to include('baz') @@ -85,8 +85,8 @@ RSpec.describe "User sorts issues" do describe 'sorting by due date', :js do before do - issue1.update(due_date: 1.day.from_now) - issue2.update(due_date: 6.days.from_now) + issue1.update!(due_date: 1.day.from_now) + issue2.update!(due_date: 6.days.from_now) end it 'sorts by due date' do @@ -96,7 +96,7 @@ RSpec.describe "User sorts issues" do end it 'sorts by due date by excluding nil due dates' do - issue2.update(due_date: nil) + issue2.update!(due_date: nil) visit project_issues_path(project, sort: sort_value_due_date) @@ -111,7 +111,7 @@ RSpec.describe "User sorts issues" do end it 'sorts by least recently due date by excluding nil due dates' do - issue2.update(due_date: nil) + issue2.update!(due_date: nil) visit project_issues_path(project, label_names: [label.name], sort: sort_value_due_date_later) @@ -122,8 +122,8 @@ RSpec.describe "User sorts issues" do describe 'filtering by due date', :js do before do - issue1.update(due_date: 1.day.from_now) - issue2.update(due_date: 6.days.from_now) + issue1.update!(due_date: 1.day.from_now) + issue2.update!(due_date: 6.days.from_now) end it 'filters by none' do @@ -147,9 +147,9 @@ RSpec.describe "User sorts issues" do 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) + 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) @@ -161,9 +161,9 @@ RSpec.describe "User sorts issues" do 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) + 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) @@ -175,9 +175,9 @@ RSpec.describe "User sorts issues" do 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) + 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) @@ -189,9 +189,9 @@ RSpec.describe "User sorts issues" do 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) + 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) @@ -206,9 +206,9 @@ RSpec.describe "User sorts issues" do describe 'sorting by milestone', :js do before do issue1.milestone = newer_due_milestone - issue1.save + issue1.save! issue2.milestone = later_due_milestone - issue2.save + issue2.save! end it 'sorts by milestone' do @@ -224,9 +224,9 @@ RSpec.describe "User sorts issues" do before do issue1.assignees << user2 - issue1.save + issue1.save! issue2.assignees << user2 - issue2.save + issue2.save! end it 'sorts with a filter applied' do |