Commit f1a48967 authored by Coung Ngo's avatar Coung Ngo Committed by Rémy Coutable

Improve `gfm_autocomplete_spec.rb` performance

Improve performance by removing nested `visit` calls
and extracting creation of objects to `let_it_be`
parent c53c936d
...@@ -7,12 +7,14 @@ RSpec.describe 'GFM autocomplete', :js do ...@@ -7,12 +7,14 @@ RSpec.describe 'GFM autocomplete', :js do
let_it_be(:user_xss) { create(:user, name: user_xss_title, username: 'xss.user') } 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(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
let_it_be(:user2) { create(:user, name: 'Marge Simpson', username: 'msimpson') } let_it_be(:user2) { create(:user, name: 'Marge Simpson', username: 'msimpson') }
let_it_be(:group) { create(:group, name: 'Ancestor') } let_it_be(:group) { create(:group, name: 'Ancestor') }
let_it_be(:child_group) { create(:group, parent: group, name: 'My group') } let_it_be(:child_group) { create(:group, parent: group, name: 'My group') }
let_it_be(:project) { create(:project, group: child_group) } let_it_be(:project) { create(:project, group: child_group) }
let_it_be(:label) { create(:label, project: project, title: 'special+') }
let(:issue) { create(:issue, project: project) } let_it_be(:issue) { create(:issue, project: project, assignees: [user]) }
let_it_be(:label) { create(:label, project: project, title: 'special+') }
let_it_be(:snippet) { create(:project_snippet, project: project, title: 'code snippet') }
before_all do before_all do
project.add_maintainer(user) project.add_maintainer(user)
...@@ -21,16 +23,35 @@ RSpec.describe 'GFM autocomplete', :js do ...@@ -21,16 +23,35 @@ RSpec.describe 'GFM autocomplete', :js do
end end
describe 'when tribute_autocomplete feature flag is off' do describe 'when tribute_autocomplete feature flag is off' do
before do describe 'new issue page' do
stub_feature_flags(tribute_autocomplete: false) before do
stub_feature_flags(tribute_autocomplete: false)
sign_in(user)
visit new_project_issue_path(project)
wait_for_requests
end
sign_in(user) it 'allows quick actions' do
visit project_issue_path(project, issue) fill_in 'Description', with: '/'
wait_for_requests expect(find_autocomplete_menu).to be_visible
end
end end
describe 'issue description' do describe 'issue description' do
let(:issue_to_edit) { create(:issue, project: project) }
before do
stub_feature_flags(tribute_autocomplete: false)
sign_in(user)
visit project_issue_path(project, issue_to_edit)
wait_for_requests
end
it 'updates with GFM reference' do it 'updates with GFM reference' do
click_button 'Edit title and description' click_button 'Edit title and description'
...@@ -58,367 +79,355 @@ RSpec.describe 'GFM autocomplete', :js do ...@@ -58,367 +79,355 @@ RSpec.describe 'GFM autocomplete', :js do
end end
end end
describe 'triggering autocomplete' do describe 'issue comment' do
it 'only opens autocomplete menu when trigger character is after whitespace', :aggregate_failures do before do
fill_in 'Comment', with: 'testing@' stub_feature_flags(tribute_autocomplete: false)
expect(page).not_to have_css('.atwho-view')
fill_in 'Comment', with: '@@'
expect(page).not_to have_css('.atwho-view')
fill_in 'Comment', with: "@#{user.username[0..2]}!"
expect(page).not_to have_css('.atwho-view')
fill_in 'Comment', with: "hello:#{user.username[0..2]}"
expect(page).not_to have_css('.atwho-view')
fill_in 'Comment', with: '7:'
expect(page).not_to have_css('.atwho-view')
fill_in 'Comment', with: 'w:'
expect(page).not_to have_css('.atwho-view')
fill_in 'Comment', with: 'Ё:' sign_in(user)
expect(page).not_to have_css('.atwho-view') visit project_issue_path(project, issue)
fill_in 'Comment', with: "test\n\n@" wait_for_requests
expect(find_autocomplete_menu).to be_visible
end end
end
it 'opens autocomplete menu when field starts with text' do describe 'triggering autocomplete' do
fill_in 'Comment', with: '@' 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')
expect(find_autocomplete_menu).to be_visible fill_in 'Comment', with: '@@'
end expect(page).not_to have_css('.atwho-view')
it 'opens autocomplete menu for Issues when field starts with text with item escaping HTML characters' do fill_in 'Comment', with: "@#{user.username[0..2]}!"
issue_xss_title = 'This will execute alert<img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;' expect(page).not_to have_css('.atwho-view')
create(:issue, project: project, title: issue_xss_title)
fill_in 'Comment', with: '#' 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_autocomplete_menu).to have_text(issue_xss_title) fill_in 'Comment', with: 'w:'
end expect(page).not_to have_css('.atwho-view')
it 'opens autocomplete menu for Username when field starts with text with item escaping HTML characters' do fill_in 'Comment', with: 'Ё:'
fill_in 'Comment', with: '@ev' expect(page).not_to have_css('.atwho-view')
wait_for_requests fill_in 'Comment', with: "test\n\n@"
expect(find_autocomplete_menu).to be_visible
end
end
expect(find_highlighted_autocomplete_item).to have_text(user_xss.username) it 'opens autocomplete menu when field starts with text' do
end fill_in 'Comment', with: '@'
it 'opens autocomplete menu for Milestone when field starts with text with item escaping HTML characters' do expect(find_autocomplete_menu).to be_visible
milestone_xss_title = 'alert milestone &lt;img src=x onerror="alert(\'Hello xss\');" a' end
create(:milestone, project: project, title: milestone_xss_title)
fill_in 'Comment', with: '%' 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)&lt;img src=x onerror=alert(1)&gt;'
create(:issue, project: project, title: issue_xss_title)
wait_for_requests fill_in 'Comment', with: '#'
expect(find_autocomplete_menu).to have_text('alert milestone') wait_for_requests
end
it 'doesnt select the first item for non-assignee dropdowns' do expect(find_autocomplete_menu).to have_text(issue_xss_title)
fill_in 'Comment', with: ':' end
wait_for_requests it 'opens autocomplete menu for Username when field starts with text with item escaping HTML characters' do
fill_in 'Comment', with: '@ev'
expect(find_autocomplete_menu).not_to have_css('.cur') wait_for_requests
end
it 'selects the first item for assignee dropdowns' do expect(find_highlighted_autocomplete_item).to have_text(user_xss.username)
fill_in 'Comment', with: '@' end
wait_for_requests it 'opens autocomplete menu for Milestone when field starts with text with item escaping HTML characters' do
milestone_xss_title = 'alert milestone &lt;img src=x onerror="alert(\'Hello xss\');" a'
create(:milestone, project: project, title: milestone_xss_title)
expect(find_autocomplete_menu).to have_css('.cur:first-of-type') fill_in 'Comment', with: '%'
end
it 'includes items for assignee dropdowns with non-ASCII characters in name' do wait_for_requests
fill_in 'Comment', with: "@#{user.name[0...8]}"
wait_for_requests expect(find_autocomplete_menu).to have_text('alert milestone')
end
expect(find_autocomplete_menu).to have_text(user.name) it 'doesnt select the first item for non-assignee dropdowns' do
end fill_in 'Comment', with: ':'
it 'searches across full name for assignees' do wait_for_requests
fill_in 'Comment', with: '@speciąlsome'
wait_for_requests expect(find_autocomplete_menu).not_to have_css('.cur')
end
expect(find_highlighted_autocomplete_item).to have_text(user.name) it 'selects the first item for assignee dropdowns' do
end fill_in 'Comment', with: '@'
it 'shows names that start with the query as the top result' do wait_for_requests
fill_in 'Comment', with: '@mar'
wait_for_requests expect(find_autocomplete_menu).to have_css('.cur:first-of-type')
end
expect(find_highlighted_autocomplete_item).to have_text(user2.name) it 'includes items for assignee dropdowns with non-ASCII characters in name' do
end fill_in 'Comment', with: "@#{user.name[0...8]}"
it 'shows usernames that start with the query as the top result' do wait_for_requests
fill_in 'Comment', with: '@msi'
wait_for_requests expect(find_autocomplete_menu).to have_text(user.name)
end
expect(find_highlighted_autocomplete_item).to have_text(user2.name) it 'searches across full name for assignees' do
end fill_in 'Comment', with: '@speciąlsome'
# Regression test for https://gitlab.com/gitlab-org/gitlab/-/issues/321925 wait_for_requests
it 'shows username when pasting then pressing Enter' do
fill_in 'Comment', with: "@#{user.username}\n"
expect(find_field('Comment').value).to have_text "@#{user.username}" expect(find_highlighted_autocomplete_item).to have_text(user.name)
end end
it 'does not show `@undefined` when pressing `@` then Enter' do it 'shows names that start with the query as the top result' do
fill_in 'Comment', with: "@\n" fill_in 'Comment', with: '@mar'
expect(find_field('Comment').value).to have_text '@' wait_for_requests
expect(find_field('Comment').value).not_to have_text '@undefined'
end
it 'selects the first item for non-assignee dropdowns if a query is entered' do expect(find_highlighted_autocomplete_item).to have_text(user2.name)
fill_in 'Comment', with: ':1' end
wait_for_requests it 'shows usernames that start with the query as the top result' do
fill_in 'Comment', with: '@msi'
expect(find_autocomplete_menu).to have_css('.cur:first-of-type') wait_for_requests
end
context 'if a selected value has special characters' do expect(find_highlighted_autocomplete_item).to have_text(user2.name)
it 'wraps the result in double quotes' do end
fill_in 'Comment', with: "~#{label.title[0]}"
find_highlighted_autocomplete_item.click # 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"
expect(find_field('Comment').value).to have_text("~\"#{label.title}\"") expect(find_field('Comment').value).to have_text "@#{user.username}"
end end
it 'doesn\'t wrap for assignee values' do it 'does not show `@undefined` when pressing `@` then Enter' do
fill_in 'Comment', with: "@#{user.username[0]}" fill_in 'Comment', with: "@\n"
find_highlighted_autocomplete_item.click expect(find_field('Comment').value).to have_text '@'
expect(find_field('Comment').value).not_to have_text '@undefined'
expect(find_field('Comment').value).to have_text("@#{user.username}")
end end
it 'doesn\'t wrap for emoji values' do it 'selects the first item for non-assignee dropdowns if a query is entered' do
fill_in 'Comment', with: ':cartwheel_' fill_in 'Comment', with: ':1'
find_highlighted_autocomplete_item.click wait_for_requests
expect(find_field('Comment').value).to have_text('cartwheel_tone1') expect(find_autocomplete_menu).to have_css('.cur:first-of-type')
end end
it 'triggers autocomplete after selecting a quick action' do context 'if a selected value has special characters' do
fill_in 'Comment', with: '/as' it 'wraps the result in double quotes' do
fill_in 'Comment', with: "~#{label.title[0]}"
find_highlighted_autocomplete_item.click find_highlighted_autocomplete_item.click
expect(find_autocomplete_menu).to have_text(user.username) expect(find_field('Comment').value).to have_text("~\"#{label.title}\"")
end end
it 'does not limit quick actions autocomplete list to 5' do it 'doesn\'t wrap for assignee values' do
fill_in 'Comment', with: '/' fill_in 'Comment', with: "@#{user.username[0]}"
expect(find_autocomplete_menu).to have_css('li', minimum: 6) find_highlighted_autocomplete_item.click
end
end
context 'assignees' do expect(find_field('Comment').value).to have_text("@#{user.username}")
let(:issue_assignee) { create(:issue, project: project) } end
let(:unassigned_user) { create(:user) }
before do it 'doesn\'t wrap for emoji values' do
issue_assignee.update(assignees: [user]) fill_in 'Comment', with: ':cartwheel_'
project.add_maintainer(unassigned_user) find_highlighted_autocomplete_item.click
end
it 'lists users who are currently not assigned to the issue when using /assign' do expect(find_field('Comment').value).to have_text('cartwheel_tone1')
visit project_issue_path(project, issue_assignee) end
fill_in 'Comment', with: '/as' it 'triggers autocomplete after selecting a quick action' do
fill_in 'Comment', with: '/as'
find_highlighted_autocomplete_item.click find_highlighted_autocomplete_item.click
expect(find_autocomplete_menu).not_to have_text(user.username) expect(find_autocomplete_menu).to have_text(user2.username)
expect(find_autocomplete_menu).to have_text(unassigned_user.username) end
end
it 'shows dropdown on new issue form' do it 'does not limit quick actions autocomplete list to 5' do
visit new_project_issue_path(project) fill_in 'Comment', with: '/'
fill_in 'Description', with: '/ass' expect(find_autocomplete_menu).to have_css('li', minimum: 6)
end
end
find_highlighted_autocomplete_item.click context 'assignees' do
it 'lists users who are currently not assigned to the issue when using /assign' do
fill_in 'Comment', with: '/as'
expect(find_autocomplete_menu).to have_text(unassigned_user.username) find_highlighted_autocomplete_item.click
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 context 'labels' do
it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do
label_xss_title = 'alert label &lt;img src=x onerror="alert(\'Hello xss\');" a' label_xss_title = 'alert label &lt;img src=x onerror="alert(\'Hello xss\');" a'
create(:label, project: project, title: label_xss_title) create(:label, project: project, title: label_xss_title)
fill_in 'Comment', with: '~' fill_in 'Comment', with: '~'
wait_for_requests wait_for_requests
expect(find_autocomplete_menu).to have_text('alert label') expect(find_autocomplete_menu).to have_text('alert label')
end end
it 'allows colons when autocompleting scoped labels' do it 'allows colons when autocompleting scoped labels' do
create(:label, project: project, title: 'scoped:label') create(:label, project: project, title: 'scoped:label')
fill_in 'Comment', with: '~scoped:' fill_in 'Comment', with: '~scoped:'
wait_for_requests wait_for_requests
expect(find_autocomplete_menu).to have_text('scoped:label') expect(find_autocomplete_menu).to have_text('scoped:label')
end end
it 'allows colons when autocompleting scoped labels with double colons' do it 'allows colons when autocompleting scoped labels with double colons' do
create(:label, project: project, title: 'scoped::label') create(:label, project: project, title: 'scoped::label')
fill_in 'Comment', with: '~scoped::' fill_in 'Comment', with: '~scoped::'
wait_for_requests wait_for_requests
expect(find_autocomplete_menu).to have_text('scoped::label') expect(find_autocomplete_menu).to have_text('scoped::label')
end end
it 'allows spaces when autocompleting multi-word labels' do it 'allows spaces when autocompleting multi-word labels' do
create(:label, project: project, title: 'Accepting merge requests') create(:label, project: project, title: 'Accepting merge requests')
fill_in 'Comment', with: '~Accepting merge' fill_in 'Comment', with: '~Accepting merge'
wait_for_requests wait_for_requests
expect(find_autocomplete_menu).to have_text('Accepting merge requests') expect(find_autocomplete_menu).to have_text('Accepting merge requests')
end end
it 'only autocompletes the latest label' do it 'only autocompletes the latest label' do
create(:label, project: project, title: 'Accepting merge requests') create(:label, project: project, title: 'Accepting merge requests')
create(:label, project: project, title: 'Accepting job applicants') create(:label, project: project, title: 'Accepting job applicants')
fill_in 'Comment', with: '~Accepting merge requests foo bar ~Accepting job' fill_in 'Comment', with: '~Accepting merge requests foo bar ~Accepting job'
wait_for_requests wait_for_requests
expect(find_autocomplete_menu).to have_text('Accepting job applicants') expect(find_autocomplete_menu).to have_text('Accepting job applicants')
end end
it 'does not autocomplete labels if no tilde is typed' do it 'does not autocomplete labels if no tilde is typed' do
create(:label, project: project, title: 'Accepting merge requests') create(:label, project: project, title: 'Accepting merge requests')
fill_in 'Comment', with: 'Accepting merge' 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
end
context 'when other notes are destroyed' do context 'when other notes are destroyed' do
let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) } 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 # This is meant to protect against this issue https://gitlab.com/gitlab-org/gitlab/-/issues/228729
it 'keeps autocomplete key listeners' do it 'keeps autocomplete key listeners' do
visit project_issue_path(project, issue) note = find_field('Comment')
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: '') note.fill_in(with: '')
start_comment_with_emoji(note, '.atwho-view li') start_comment_with_emoji(note, '.atwho-view li')
note.native.send_keys(:enter) note.native.send_keys(:enter)
expect(note.value).to eql('Hello :100: ') expect(note.value).to eql('Hello :100: ')
end
end end
end
shared_examples 'autocomplete suggestions' do shared_examples 'autocomplete suggestions' do
it 'suggests objects correctly' do it 'suggests objects correctly' do
fill_in 'Comment', with: object.class.reference_prefix 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
end
context 'issues' do context 'issues' do
let(:object) { issue } let(:object) { issue }
let(:expected_body) { object.to_reference } let(:expected_body) { object.to_reference }
it_behaves_like 'autocomplete suggestions' it_behaves_like 'autocomplete suggestions'
end end
context 'merge requests' do context 'merge requests' do
let(:object) { create(:merge_request, source_project: project) } let(:object) { create(:merge_request, source_project: project) }
let(:expected_body) { object.to_reference } let(:expected_body) { object.to_reference }
it_behaves_like 'autocomplete suggestions' it_behaves_like 'autocomplete suggestions'
end end
context 'project snippets' do context 'project snippets' do
let!(:object) { create(:project_snippet, project: project, title: 'code snippet') } let!(:object) { snippet }
let(:expected_body) { object.to_reference } let(:expected_body) { object.to_reference }
it_behaves_like 'autocomplete suggestions' it_behaves_like 'autocomplete suggestions'
end end
context 'label' do context 'label' do
let!(:object) { label } let!(:object) { label }
let(:expected_body) { object.title } let(:expected_body) { object.title }
it_behaves_like 'autocomplete suggestions' it_behaves_like 'autocomplete suggestions'
end end
context 'milestone' do context 'milestone' do
let_it_be(:milestone_expired) { create(:milestone, project: project, due_date: 5.days.ago) } 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(: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(: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(: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) } let_it_be(:milestone3) { create(:milestone, project: project, title: 'Milestone-3', due_date: 10.days.from_now) }
before do before do
fill_in 'Comment', with: '/milestone %' fill_in 'Comment', with: '/milestone %'
wait_for_requests wait_for_requests
end end
it 'shows milestons list in the autocomplete menu' do it 'shows milestons list in the autocomplete menu' do
page.within(find_autocomplete_menu) do page.within(find_autocomplete_menu) do
expect(page).to have_selector('li', count: 5) expect(page).to have_selector('li', count: 5)
end
end end
end
it 'shows expired milestone at the bottom of the list' do it 'shows expired milestone at the bottom of the list' do
page.within(find_autocomplete_menu) do page.within(find_autocomplete_menu) do
expect(page.find('li:last-child')).to have_content milestone_expired.title expect(page.find('li:last-child')).to have_content milestone_expired.title
end
end end
end
it 'shows milestone due earliest at the top of the list' do it 'shows milestone due earliest at the top of the list' do
page.within(find_autocomplete_menu) do page.within(find_autocomplete_menu) do
aggregate_failures do aggregate_failures do
expect(page.all('li')[0]).to have_content milestone3.title expect(page.all('li')[0]).to have_content milestone3.title
expect(page.all('li')[1]).to have_content milestone2.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')[2]).to have_content milestone1.title
expect(page.all('li')[3]).to have_content milestone_no_duedate.title expect(page.all('li')[3]).to have_content milestone_no_duedate.title
end
end end
end end
end end
...@@ -426,16 +435,18 @@ RSpec.describe 'GFM autocomplete', :js do ...@@ -426,16 +435,18 @@ RSpec.describe 'GFM autocomplete', :js do
end end
describe 'when tribute_autocomplete feature flag is on' do describe 'when tribute_autocomplete feature flag is on' do
before do describe 'issue description' do
stub_feature_flags(tribute_autocomplete: true) let(:issue_to_edit) { create(:issue, project: project) }
sign_in(user) before do
visit project_issue_path(project, issue) stub_feature_flags(tribute_autocomplete: true)
wait_for_requests sign_in(user)
end visit project_issue_path(project, issue_to_edit)
wait_for_requests
end
describe 'issue description' do
it 'updates with GFM reference' do it 'updates with GFM reference' do
click_button 'Edit title and description' click_button 'Edit title and description'
...@@ -455,309 +466,306 @@ RSpec.describe 'GFM autocomplete', :js do ...@@ -455,309 +466,306 @@ RSpec.describe 'GFM autocomplete', :js do
end end
end end
describe 'triggering autocomplete' do describe 'issue comment' do
it 'only opens autocomplete menu when trigger character is after whitespace', :aggregate_failures do before do
fill_in 'Comment', with: 'testing@' stub_feature_flags(tribute_autocomplete: true)
expect(page).not_to have_css('.tribute-container')
fill_in 'Comment', with: "hello:#{user.username[0..2]}"
expect(page).not_to have_css('.tribute-container')
fill_in 'Comment', with: '7:'
expect(page).not_to have_css('.tribute-container')
fill_in 'Comment', with: 'w:'
expect(page).not_to have_css('.tribute-container')
fill_in 'Comment', with: 'Ё:' sign_in(user)
expect(page).not_to have_css('.tribute-container') visit project_issue_path(project, issue)
fill_in 'Comment', with: "test\n\n@" wait_for_requests
expect(find_tribute_autocomplete_menu).to be_visible
end end
end
it 'opens autocomplete menu when field starts with text' do
fill_in 'Comment', with: '@'
expect(find_tribute_autocomplete_menu).to be_visible
end
it 'opens autocomplete menu for Issues when field starts with text with item escaping HTML characters' do describe 'triggering autocomplete' do
issue_xss_title = 'This will execute alert<img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;' it 'only opens autocomplete menu when trigger character is after whitespace', :aggregate_failures do
create(:issue, project: project, title: issue_xss_title) fill_in 'Comment', with: 'testing@'
expect(page).not_to have_css('.tribute-container')
fill_in 'Comment', with: '#' fill_in 'Comment', with: "hello:#{user.username[0..2]}"
expect(page).not_to have_css('.tribute-container')
wait_for_requests fill_in 'Comment', with: '7:'
expect(page).not_to have_css('.tribute-container')
expect(find_tribute_autocomplete_menu).to have_text(issue_xss_title) fill_in 'Comment', with: 'w:'
end expect(page).not_to have_css('.tribute-container')
it 'opens autocomplete menu for Username when field starts with text with item escaping HTML characters' do fill_in 'Comment', with: 'Ё:'
fill_in 'Comment', with: '@ev' expect(page).not_to have_css('.tribute-container')
wait_for_requests fill_in 'Comment', with: "test\n\n@"
expect(find_tribute_autocomplete_menu).to be_visible
end
end
expect(find_tribute_autocomplete_menu).to have_text(user_xss.username) it 'opens autocomplete menu when field starts with text' do
end fill_in 'Comment', with: '@'
it 'opens autocomplete menu for Milestone when field starts with text with item escaping HTML characters' do expect(find_tribute_autocomplete_menu).to be_visible
milestone_xss_title = 'alert milestone &lt;img src=x onerror="alert(\'Hello xss\');" a' end
create(:milestone, project: project, title: milestone_xss_title)
fill_in 'Comment', with: '%' 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)&lt;img src=x onerror=alert(1)&gt;'
create(:issue, project: project, title: issue_xss_title)
wait_for_requests fill_in 'Comment', with: '#'
expect(find_tribute_autocomplete_menu).to have_text('alert milestone') wait_for_requests
end
it 'selects the first item for assignee dropdowns' do expect(find_tribute_autocomplete_menu).to have_text(issue_xss_title)
fill_in 'Comment', with: '@' end
wait_for_requests it 'opens autocomplete menu for Username when field starts with text with item escaping HTML characters' do
fill_in 'Comment', with: '@ev'
expect(find_tribute_autocomplete_menu).to have_css('.highlight:first-of-type') wait_for_requests
end
it 'includes items for assignee dropdowns with non-ASCII characters in name' do expect(find_tribute_autocomplete_menu).to have_text(user_xss.username)
fill_in 'Comment', with: "@#{user.name[0...8]}" end
wait_for_requests it 'opens autocomplete menu for Milestone when field starts with text with item escaping HTML characters' do
milestone_xss_title = 'alert milestone &lt;img src=x onerror="alert(\'Hello xss\');" a'
create(:milestone, project: project, title: milestone_xss_title)
expect(find_tribute_autocomplete_menu).to have_text(user.name) fill_in 'Comment', with: '%'
end
it 'selects the first item for non-assignee dropdowns if a query is entered' do wait_for_requests
fill_in 'Comment', with: ':1'
wait_for_requests expect(find_tribute_autocomplete_menu).to have_text('alert milestone')
end
expect(find_tribute_autocomplete_menu).to have_css('.highlight:first-of-type') it 'selects the first item for assignee dropdowns' do
end fill_in 'Comment', with: '@'
context 'when autocompleting for groups' do wait_for_requests
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('My group') expect(find_tribute_autocomplete_menu).to have_css('.highlight:first-of-type')
end end
it 'does not show the group when searching for the name of the parent of the group' do it 'includes items for assignee dropdowns with non-ASCII characters in name' do
fill_in 'Comment', with: '@ancestor' fill_in 'Comment', with: "@#{user.name[0...8]}"
expect(find_tribute_autocomplete_menu).not_to have_text('My group') wait_for_requests
expect(find_tribute_autocomplete_menu).to have_text(user.name)
end end
end
context 'if a selected value has special characters' do it 'selects the first item for non-assignee dropdowns if a query is entered' do
it 'wraps the result in double quotes' do fill_in 'Comment', with: ':1'
fill_in 'Comment', with: "~#{label.title[0]}"
find_highlighted_tribute_autocomplete_menu.click wait_for_requests
expect(find_field('Comment').value).to have_text("~\"#{label.title}\"") expect(find_tribute_autocomplete_menu).to have_css('.highlight:first-of-type')
end end
it 'doesn\'t wrap for assignee values' do context 'when autocompleting for groups' do
fill_in 'Comment', with: "@#{user.username[0..2]}" it 'shows the group when searching for the name of the group' do
fill_in 'Comment', with: '@mygroup'
find_highlighted_tribute_autocomplete_menu.click expect(find_tribute_autocomplete_menu).to have_text('My group')
end
it 'does not show the group when searching for the name of the parent of the group' do
fill_in 'Comment', with: '@ancestor'
expect(find_field('Comment').value).to have_text("@#{user.username}") expect(find_tribute_autocomplete_menu).not_to have_text('My group')
end
end end
it 'does not wrap for emoji values' do context 'if a selected value has special characters' do
fill_in 'Comment', with: ':cartwheel_' it 'wraps the result in double quotes' do
fill_in 'Comment', with: "~#{label.title[0]}"
find_highlighted_tribute_autocomplete_menu.click find_highlighted_tribute_autocomplete_menu.click
expect(find_field('Comment').value).to have_text('cartwheel_tone1') expect(find_field('Comment').value).to have_text("~\"#{label.title}\"")
end end
it 'autocompletes for quick actions' do it 'doesn\'t wrap for assignee values' do
fill_in 'Comment', with: '/as' 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('/assign') expect(find_field('Comment').value).to have_text("@#{user.username}")
end end
end
context 'assignees' do it 'does not wrap for emoji values' do
let(:issue_assignee) { create(:issue, project: project) } fill_in 'Comment', with: ':cartwheel_'
let(:unassigned_user) { create(:user) }
before do find_highlighted_tribute_autocomplete_menu.click
issue_assignee.update(assignees: [user])
project.add_maintainer(unassigned_user) 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' do it 'autocompletes for quick actions' do
visit project_issue_path(project, issue_assignee) fill_in 'Comment', with: '/as'
note = find_field('Comment') find_highlighted_tribute_autocomplete_menu.click
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_field('Comment').value).to have_text('/assign')
expect(find_tribute_autocomplete_menu).to have_text(unassigned_user.username) end
end end
it 'lists users who are currently not assigned to the issue when using /assign on the second line' do context 'assignees' do
visit project_issue_path(project, issue_assignee) it 'lists users who are currently not assigned to the issue when using /assign' 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
note = find_field('Comment') expect(find_tribute_autocomplete_menu).not_to have_text(user.username)
note.native.send_keys('/assign @user2') expect(find_tribute_autocomplete_menu).to have_text(user2.username)
note.native.send_keys(:enter) end
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) it 'lists users who are currently not assigned to the issue when using /assign on the second line' do
expect(find_tribute_autocomplete_menu).to have_text(unassigned_user.username) 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 'labels' do context 'labels' do
it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do
label_xss_title = 'alert label &lt;img src=x onerror="alert(\'Hello xss\');" a' label_xss_title = 'alert label &lt;img src=x onerror="alert(\'Hello xss\');" a'
create(:label, project: project, title: label_xss_title) create(:label, project: project, title: label_xss_title)
fill_in 'Comment', with: '~' fill_in 'Comment', with: '~'
wait_for_requests wait_for_requests
expect(find_tribute_autocomplete_menu).to have_text('alert label') expect(find_tribute_autocomplete_menu).to have_text('alert label')
end end
it 'allows colons when autocompleting scoped labels' do it 'allows colons when autocompleting scoped labels' do
create(:label, project: project, title: 'scoped:label') create(:label, project: project, title: 'scoped:label')
fill_in 'Comment', with: '~scoped:' fill_in 'Comment', with: '~scoped:'
wait_for_requests wait_for_requests
expect(find_tribute_autocomplete_menu).to have_text('scoped:label') expect(find_tribute_autocomplete_menu).to have_text('scoped:label')
end end
it 'allows colons when autocompleting scoped labels with double colons' do it 'allows colons when autocompleting scoped labels with double colons' do
create(:label, project: project, title: 'scoped::label') create(:label, project: project, title: 'scoped::label')
fill_in 'Comment', with: '~scoped::' fill_in 'Comment', with: '~scoped::'
wait_for_requests wait_for_requests
expect(find_tribute_autocomplete_menu).to have_text('scoped::label') expect(find_tribute_autocomplete_menu).to have_text('scoped::label')
end end
it 'autocompletes multi-word labels' do it 'autocompletes multi-word labels' do
create(:label, project: project, title: 'Accepting merge requests') create(:label, project: project, title: 'Accepting merge requests')
fill_in 'Comment', with: '~Acceptingmerge' fill_in 'Comment', with: '~Acceptingmerge'
wait_for_requests wait_for_requests
expect(find_tribute_autocomplete_menu).to have_text('Accepting merge requests') expect(find_tribute_autocomplete_menu).to have_text('Accepting merge requests')
end end
it 'only autocompletes the latest label' do it 'only autocompletes the latest label' do
create(:label, project: project, title: 'documentation') create(:label, project: project, title: 'documentation')
create(:label, project: project, title: 'feature') create(:label, project: project, title: 'feature')
fill_in 'Comment', with: '~documentation foo bar ~feat' fill_in 'Comment', with: '~documentation foo bar ~feat'
# Invoke autocompletion # Invoke autocompletion
find_field('Comment').native.send_keys(:right) find_field('Comment').native.send_keys(:right)
wait_for_requests wait_for_requests
expect(find_tribute_autocomplete_menu).to have_text('feature') expect(find_tribute_autocomplete_menu).to have_text('feature')
expect(find_tribute_autocomplete_menu).not_to have_text('documentation') expect(find_tribute_autocomplete_menu).not_to have_text('documentation')
end end
it 'does not autocomplete labels if no tilde is typed' do it 'does not autocomplete labels if no tilde is typed' do
create(:label, project: project, title: 'documentation') create(:label, project: project, title: 'documentation')
fill_in 'Comment', with: 'document' fill_in 'Comment', with: 'document'
wait_for_requests wait_for_requests
expect(page).not_to have_css('.tribute-container') expect(page).not_to have_css('.tribute-container')
end
end end
end
context 'when other notes are destroyed' do context 'when other notes are destroyed' do
let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) } 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 # This is meant to protect against this issue https://gitlab.com/gitlab-org/gitlab/-/issues/228729
it 'keeps autocomplete key listeners' do it 'keeps autocomplete key listeners' do
visit project_issue_path(project, issue) note = find_field('Comment')
note = find_field('Comment')
start_comment_with_emoji(note, '.tribute-container li') start_comment_with_emoji(note, '.tribute-container li')
start_and_cancel_discussion start_and_cancel_discussion
note.fill_in(with: '') note.fill_in(with: '')
start_comment_with_emoji(note, '.tribute-container li') start_comment_with_emoji(note, '.tribute-container li')
note.native.send_keys(:enter) note.native.send_keys(:enter)
expect(note.value).to eql('Hello :100: ') expect(note.value).to eql('Hello :100: ')
end
end end
end
shared_examples 'autocomplete suggestions' do shared_examples 'autocomplete suggestions' do
it 'suggests objects correctly' do it 'suggests objects correctly' do
fill_in 'Comment', with: object.class.reference_prefix fill_in 'Comment', with: object.class.reference_prefix
find_tribute_autocomplete_menu.find('li').click find_tribute_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
end
context 'issues' do context 'issues' do
let(:object) { issue } let(:object) { issue }
let(:expected_body) { object.to_reference } let(:expected_body) { object.to_reference }
it_behaves_like 'autocomplete suggestions' it_behaves_like 'autocomplete suggestions'
end end
context 'merge requests' do context 'merge requests' do
let(:object) { create(:merge_request, source_project: project) } let(:object) { create(:merge_request, source_project: project) }
let(:expected_body) { object.to_reference } let(:expected_body) { object.to_reference }
it_behaves_like 'autocomplete suggestions' it_behaves_like 'autocomplete suggestions'
end end
context 'project snippets' do context 'project snippets' do
let!(:object) { create(:project_snippet, project: project, title: 'code snippet') } let!(:object) { snippet }
let(:expected_body) { object.to_reference } let(:expected_body) { object.to_reference }
it_behaves_like 'autocomplete suggestions' it_behaves_like 'autocomplete suggestions'
end end
context 'label' do context 'label' do
let!(:object) { label } let!(:object) { label }
let(:expected_body) { object.title } let(:expected_body) { object.title }
it_behaves_like 'autocomplete suggestions' it_behaves_like 'autocomplete suggestions'
end end
context 'milestone' do context 'milestone' do
let!(:object) { create(:milestone, project: project) } let!(:object) { create(:milestone, project: project) }
let(:expected_body) { object.to_reference } let(:expected_body) { object.to_reference }
it_behaves_like 'autocomplete suggestions' it_behaves_like 'autocomplete suggestions'
end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment