diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-02 13:03:23 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-02 13:03:23 +0000 |
commit | a72a9af092c1bfcf9f8024d59c11cf222f07e1e7 (patch) | |
tree | 44b60265c1d476d026b2862d2c1244748f558d4f /spec | |
parent | b085478c4c2bed74fdc6eb2c33bfc62e791baf03 (diff) | |
download | gitlab-ce-a72a9af092c1bfcf9f8024d59c11cf222f07e1e7.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
47 files changed, 622 insertions, 249 deletions
diff --git a/spec/factories/resource_weight_events.rb b/spec/factories/resource_weight_events.rb new file mode 100644 index 00000000000..cb9a34df332 --- /dev/null +++ b/spec/factories/resource_weight_events.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :resource_weight_event do + issue { create(:issue) } + user { issue&.author || create(:user) } + end +end diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index 0d5f5df71b6..6bcadda6523 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -57,7 +57,7 @@ describe "Admin Runners" do expect(page).to have_content 'runner-active' expect(page).to have_content 'runner-paused' - input_filtered_search_keys('status:active') + input_filtered_search_keys('status=active') expect(page).to have_content 'runner-active' expect(page).not_to have_content 'runner-paused' end @@ -68,7 +68,7 @@ describe "Admin Runners" do visit admin_runners_path - input_filtered_search_keys('status:offline') + input_filtered_search_keys('status=offline') expect(page).not_to have_content 'runner-active' expect(page).not_to have_content 'runner-paused' @@ -83,12 +83,12 @@ describe "Admin Runners" do visit admin_runners_path - input_filtered_search_keys('status:active') + input_filtered_search_keys('status=active') expect(page).to have_content 'runner-a-1' expect(page).to have_content 'runner-b-1' expect(page).not_to have_content 'runner-a-2' - input_filtered_search_keys('status:active runner-a') + input_filtered_search_keys('status=active runner-a') expect(page).to have_content 'runner-a-1' expect(page).not_to have_content 'runner-b-1' expect(page).not_to have_content 'runner-a-2' @@ -105,7 +105,7 @@ describe "Admin Runners" do expect(page).to have_content 'runner-project' expect(page).to have_content 'runner-group' - input_filtered_search_keys('type:project_type') + input_filtered_search_keys('type=project_type') expect(page).to have_content 'runner-project' expect(page).not_to have_content 'runner-group' end @@ -116,7 +116,7 @@ describe "Admin Runners" do visit admin_runners_path - input_filtered_search_keys('type:instance_type') + input_filtered_search_keys('type=instance_type') expect(page).not_to have_content 'runner-project' expect(page).not_to have_content 'runner-group' @@ -131,12 +131,12 @@ describe "Admin Runners" do visit admin_runners_path - input_filtered_search_keys('type:project_type') + input_filtered_search_keys('type=project_type') expect(page).to have_content 'runner-a-1' expect(page).to have_content 'runner-b-1' expect(page).not_to have_content 'runner-a-2' - input_filtered_search_keys('type:project_type runner-a') + input_filtered_search_keys('type=project_type runner-a') expect(page).to have_content 'runner-a-1' expect(page).not_to have_content 'runner-b-1' expect(page).not_to have_content 'runner-a-2' @@ -153,7 +153,7 @@ describe "Admin Runners" do expect(page).to have_content 'runner-blue' expect(page).to have_content 'runner-red' - input_filtered_search_keys('tag:blue') + input_filtered_search_keys('tag=blue') expect(page).to have_content 'runner-blue' expect(page).not_to have_content 'runner-red' @@ -165,7 +165,7 @@ describe "Admin Runners" do visit admin_runners_path - input_filtered_search_keys('tag:red') + input_filtered_search_keys('tag=red') expect(page).not_to have_content 'runner-blue' expect(page).not_to have_content 'runner-blue' @@ -179,13 +179,13 @@ describe "Admin Runners" do visit admin_runners_path - input_filtered_search_keys('tag:blue') + input_filtered_search_keys('tag=blue') expect(page).to have_content 'runner-a-1' expect(page).to have_content 'runner-b-1' expect(page).not_to have_content 'runner-a-2' - input_filtered_search_keys('tag:blue runner-a') + input_filtered_search_keys('tag=blue runner-a') expect(page).to have_content 'runner-a-1' expect(page).not_to have_content 'runner-b-1' diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index c740e4e26d9..a5f98e82c33 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -628,7 +628,7 @@ describe 'Issue Boards', :js do end def set_filter(type, text) - find('.filtered-search').native.send_keys("#{type}:#{text}") + find('.filtered-search').native.send_keys("#{type}=#{text}") end def submit_filter diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb index 70bc067f79d..d14041ecf3f 100644 --- a/spec/features/boards/modal_filter_spec.rb +++ b/spec/features/boards/modal_filter_spec.rb @@ -211,7 +211,7 @@ describe 'Issue Boards add issue modal filtering', :js do end def set_filter(type, text = '') - find('.add-issues-modal .filtered-search').native.send_keys("#{type}:#{text}") + find('.add-issues-modal .filtered-search').native.send_keys("#{type}=#{text}") end def submit_filter diff --git a/spec/features/dashboard/issues_filter_spec.rb b/spec/features/dashboard/issues_filter_spec.rb index 1352e1bd8fc..8e7fd1f500f 100644 --- a/spec/features/dashboard/issues_filter_spec.rb +++ b/spec/features/dashboard/issues_filter_spec.rb @@ -28,14 +28,14 @@ describe 'Dashboard Issues filtering', :js do context 'filtering by milestone' do it 'shows all issues with no milestone' do - input_filtered_search("milestone:none") + input_filtered_search("milestone=none") expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_selector('.issue', count: 1) end it 'shows all issues with the selected milestone' do - input_filtered_search("milestone:%\"#{milestone.title}\"") + input_filtered_search("milestone=%\"#{milestone.title}\"") expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_selector('.issue', count: 1) @@ -63,7 +63,7 @@ describe 'Dashboard Issues filtering', :js do let!(:label_link) { create(:label_link, label: label, target: issue) } it 'shows all issues with the selected label' do - input_filtered_search("label:~#{label.title}") + input_filtered_search("label=~#{label.title}") page.within 'ul.content-list' do expect(page).to have_content issue.title diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb index cb055ff8416..a2ead1b5d33 100644 --- a/spec/features/dashboard/issues_spec.rb +++ b/spec/features/dashboard/issues_spec.rb @@ -30,7 +30,7 @@ RSpec.describe 'Dashboard Issues' do it 'shows issues when current user is author', :js do reset_filters - input_filtered_search("author:#{current_user.to_reference}") + input_filtered_search("author=#{current_user.to_reference}") expect(page).to have_content(authored_issue.title) expect(page).to have_content(authored_issue_on_public_project.title) diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb index 0c1e1d5910b..bb515cfae82 100644 --- a/spec/features/dashboard/merge_requests_spec.rb +++ b/spec/features/dashboard/merge_requests_spec.rb @@ -107,7 +107,7 @@ describe 'Dashboard Merge Requests' do it 'shows authored merge requests', :js do reset_filters - input_filtered_search("author:#{current_user.to_reference}") + input_filtered_search("author=#{current_user.to_reference}") expect(page).to have_content(authored_merge_request.title) expect(page).to have_content(authored_merge_request_from_fork.title) @@ -120,7 +120,7 @@ describe 'Dashboard Merge Requests' do it 'shows labeled merge requests', :js do reset_filters - input_filtered_search("label:#{label.name}") + input_filtered_search("label=#{label.name}") expect(page).to have_content(labeled_merge_request.title) diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb index b9b233026fd..a3fa87e3242 100644 --- a/spec/features/groups/issues_spec.rb +++ b/spec/features/groups/issues_spec.rb @@ -48,7 +48,7 @@ describe 'Group issues page' do let(:user2) { user_outside_group } it 'filters by only group users' do - filtered_search.set('assignee:') + filtered_search.set('assignee=') expect(find('#js-dropdown-assignee .filter-dropdown')).to have_content(user.name) expect(find('#js-dropdown-assignee .filter-dropdown')).not_to have_content(user2.name) diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb index 59230d6891a..0038a8e4892 100644 --- a/spec/features/groups/merge_requests_spec.rb +++ b/spec/features/groups/merge_requests_spec.rb @@ -52,7 +52,7 @@ describe 'Group merge requests page' do let(:user2) { user_outside_group } it 'filters by assignee only group users' do - filtered_search.set('assignee:') + filtered_search.set('assignee=') expect(find('#js-dropdown-assignee .filter-dropdown')).to have_content(user.name) expect(find('#js-dropdown-assignee .filter-dropdown')).not_to have_content(user2.name) diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb index 2d7f5822996..8aa29cddd5f 100644 --- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb @@ -20,13 +20,13 @@ describe 'Dropdown assignee', :js do describe 'behavior' do it 'loads all the assignees when opened' do - input_filtered_search('assignee:', submit: false, extra_space: false) + input_filtered_search('assignee=', submit: false, extra_space: false) expect_filtered_search_dropdown_results(filter_dropdown, 2) end it 'shows current user at top of dropdown' do - input_filtered_search('assignee:', submit: false, extra_space: false) + input_filtered_search('assignee=', submit: false, extra_space: false) expect(filter_dropdown.first('.filter-dropdown-item')).to have_content(user.name) end @@ -35,7 +35,7 @@ describe 'Dropdown assignee', :js do describe 'selecting from dropdown without Ajax call' do before do Gitlab::Testing::RequestBlockerMiddleware.block_requests! - input_filtered_search('assignee:', submit: false, extra_space: false) + input_filtered_search('assignee=', submit: false, extra_space: false) end after do diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb index 6567bbcf8a2..c95bd7071b3 100644 --- a/spec/features/issues/filtered_search/dropdown_author_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb @@ -20,13 +20,13 @@ describe 'Dropdown author', :js do describe 'behavior' do it 'loads all the authors when opened' do - input_filtered_search('author:', submit: false, extra_space: false) + input_filtered_search('author=', submit: false, extra_space: false) expect_filtered_search_dropdown_results(filter_dropdown, 2) end it 'shows current user at top of dropdown' do - input_filtered_search('author:', submit: false, extra_space: false) + input_filtered_search('author=', submit: false, extra_space: false) expect(filter_dropdown.first('.filter-dropdown-item')).to have_content(user.name) end @@ -35,7 +35,7 @@ describe 'Dropdown author', :js do describe 'selecting from dropdown without Ajax call' do before do Gitlab::Testing::RequestBlockerMiddleware.block_requests! - input_filtered_search('author:', submit: false, extra_space: false) + input_filtered_search('author=', submit: false, extra_space: false) end after do diff --git a/spec/features/issues/filtered_search/dropdown_base_spec.rb b/spec/features/issues/filtered_search/dropdown_base_spec.rb index 0a8d768fe49..2a800f054a0 100644 --- a/spec/features/issues/filtered_search/dropdown_base_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_base_spec.rb @@ -27,14 +27,14 @@ describe 'Dropdown base', :js do it 'shows loading indicator when opened' do slow_requests do # We aren't using `input_filtered_search` because we want to see the loading indicator - filtered_search.set('assignee:') + filtered_search.set('assignee=') expect(page).to have_css("#{js_dropdown_assignee} .filter-dropdown-loading", visible: true) end end it 'hides loading indicator when loaded' do - input_filtered_search('assignee:', submit: false, extra_space: false) + input_filtered_search('assignee=', submit: false, extra_space: false) expect(find(js_dropdown_assignee)).not_to have_css('.filter-dropdown-loading') end @@ -42,7 +42,7 @@ describe 'Dropdown base', :js do describe 'caching requests' do it 'caches requests after the first load' do - input_filtered_search('assignee:', submit: false, extra_space: false) + input_filtered_search('assignee=', submit: false, extra_space: false) initial_size = dropdown_assignee_size expect(initial_size).to be > 0 @@ -50,7 +50,7 @@ describe 'Dropdown base', :js do new_user = create(:user) project.add_maintainer(new_user) find('.filtered-search-box .clear-search').click - input_filtered_search('assignee:', submit: false, extra_space: false) + input_filtered_search('assignee=', submit: false, extra_space: false) expect(dropdown_assignee_size).to eq(initial_size) end diff --git a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb index 324f39cbd2c..4c11f83318b 100644 --- a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb @@ -26,8 +26,8 @@ describe 'Dropdown emoji', :js do end describe 'behavior' do - it 'does not open when the search bar has my-reaction:' do - filtered_search.set('my-reaction:') + it 'does not open when the search bar has my-reaction=' do + filtered_search.set('my-reaction=') expect(page).not_to have_css(js_dropdown_emoji) end @@ -42,20 +42,20 @@ describe 'Dropdown emoji', :js do end describe 'behavior' do - it 'opens when the search bar has my-reaction:' do - filtered_search.set('my-reaction:') + it 'opens when the search bar has my-reaction=' do + filtered_search.set('my-reaction=') expect(page).to have_css(js_dropdown_emoji, visible: true) end it 'loads all the emojis when opened' do - input_filtered_search('my-reaction:', submit: false, extra_space: false) + input_filtered_search('my-reaction=', submit: false, extra_space: false) expect_filtered_search_dropdown_results(filter_dropdown, 3) end it 'shows the most populated emoji at top of dropdown' do - input_filtered_search('my-reaction:', submit: false, extra_space: false) + input_filtered_search('my-reaction=', submit: false, extra_space: false) expect(first("#{js_dropdown_emoji} .filter-dropdown li")).to have_content(award_emoji_star.name) end diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb index 5994f3a7902..10b092c6957 100644 --- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb @@ -9,11 +9,16 @@ describe 'Dropdown hint', :js do let!(:user) { create(:user) } let(:filtered_search) { find('.filtered-search') } let(:js_dropdown_hint) { '#js-dropdown-hint' } + let(:js_dropdown_operator) { '#js-dropdown-operator' } def click_hint(text) find('#js-dropdown-hint .filter-dropdown .filter-dropdown-item', text: text).click end + def click_operator(op) + find("#js-dropdown-operator .filter-dropdown .filter-dropdown-item[data-value='#{op}']").click + end + before do project.add_maintainer(user) create(:issue, project: project) @@ -27,7 +32,7 @@ describe 'Dropdown hint', :js do it 'does not exist my-reaction dropdown item' do expect(page).to have_css(js_dropdown_hint, visible: false) - expect(page).not_to have_content('my-reaction') + expect(page).not_to have_content('My-reaction') end end @@ -54,15 +59,6 @@ describe 'Dropdown hint', :js do end describe 'filtering' do - it 'does not filter `Press Enter or click to search`' do - filtered_search.set('randomtext') - - hint_dropdown = find(js_dropdown_hint) - - expect(hint_dropdown).to have_content('Press Enter or click to search') - expect(hint_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 0) - end - it 'filters with text' do filtered_search.set('a') @@ -76,21 +72,27 @@ describe 'Dropdown hint', :js do end it 'opens the token dropdown when you click on it' do - click_hint('author') + click_hint('Author') expect(page).to have_css(js_dropdown_hint, visible: false) + expect(page).to have_css(js_dropdown_operator, visible: true) + + click_operator('=') + + expect(page).to have_css(js_dropdown_hint, visible: false) + expect(page).to have_css(js_dropdown_operator, visible: false) expect(page).to have_css('#js-dropdown-author', visible: true) - expect_tokens([{ name: 'Author' }]) + expect_tokens([{ name: 'Author', operator: '=' }]) expect_filtered_search_input_empty end end describe 'reselecting from dropdown' do it 'reuses existing token text' do - filtered_search.send_keys('author:') + filtered_search.send_keys('author') filtered_search.send_keys(:backspace) filtered_search.send_keys(:backspace) - click_hint('author') + click_hint('Author') expect_tokens([{ name: 'Author' }]) expect_filtered_search_input_empty diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb index 45112b01eac..1e90efc8d56 100644 --- a/spec/features/issues/filtered_search/dropdown_label_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb @@ -21,7 +21,7 @@ describe 'Dropdown label', :js do describe 'behavior' do it 'loads all the labels when opened' do create(:label, project: project, title: 'bug-label') - filtered_search.set('label:') + filtered_search.set('label=') expect_filtered_search_dropdown_results(filter_dropdown, 1) end diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb index 2f18aa8abaa..1f62a8e0c8d 100644 --- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb @@ -23,7 +23,7 @@ describe 'Dropdown milestone', :js do describe 'behavior' do before do - filtered_search.set('milestone:') + filtered_search.set('milestone=') end it 'loads all the milestones when opened' do diff --git a/spec/features/issues/filtered_search/dropdown_release_spec.rb b/spec/features/issues/filtered_search/dropdown_release_spec.rb index b9cce5c6998..fd0a98f9ddc 100644 --- a/spec/features/issues/filtered_search/dropdown_release_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_release_spec.rb @@ -23,7 +23,7 @@ describe 'Dropdown release', :js do describe 'behavior' do before do - filtered_search.set('release:') + filtered_search.set('release=') end it 'loads all the releases when opened' do diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index 12e010e293a..c99c205d5da 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -67,7 +67,7 @@ describe 'Filter issues', :js do it 'filters by all available tokens' do search_term = 'issue' - input_filtered_search("assignee:@#{user.username} author:@#{user.username} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} #{search_term}") + input_filtered_search("assignee=@#{user.username} author=@#{user.username} label=~#{caps_sensitive_label.title} milestone=%#{milestone.title} #{search_term}") wait_for_requests @@ -84,7 +84,7 @@ describe 'Filter issues', :js do describe 'filter issues by author' do context 'only author' do it 'filters issues by searched author' do - input_filtered_search("author:@#{user.username}") + input_filtered_search("author=@#{user.username}") wait_for_requests @@ -98,7 +98,7 @@ describe 'Filter issues', :js do describe 'filter issues by assignee' do context 'only assignee' do it 'filters issues by searched assignee' do - input_filtered_search("assignee:@#{user.username}") + input_filtered_search("assignee=@#{user.username}") wait_for_requests @@ -108,7 +108,7 @@ describe 'Filter issues', :js do end it 'filters issues by no assignee' do - input_filtered_search('assignee:none') + input_filtered_search('assignee=none') expect_tokens([assignee_token('None')]) expect_issues_list_count(3) @@ -122,7 +122,7 @@ describe 'Filter issues', :js do it 'filters issues by multiple assignees' do create(:issue, project: project, author: user, assignees: [user2, user]) - input_filtered_search("assignee:@#{user.username} assignee:@#{user2.username}") + input_filtered_search("assignee=@#{user.username} assignee=@#{user2.username}") expect_tokens([ assignee_token(user.name), @@ -138,15 +138,31 @@ describe 'Filter issues', :js do describe 'filter issues by label' do context 'only label' do it 'filters issues by searched label' do - input_filtered_search("label:~#{bug_label.title}") + input_filtered_search("label=~#{bug_label.title}") expect_tokens([label_token(bug_label.title)]) expect_issues_list_count(2) expect_filtered_search_input_empty end + it 'filters issues not containing searched label' do + input_filtered_search("label!=~#{bug_label.title}") + + expect_tokens([label_token(bug_label.title)]) + expect_issues_list_count(6) + expect_filtered_search_input_empty + end + + it 'filters issues by no label' do + input_filtered_search('label=none') + + expect_tokens([label_token('None', false)]) + expect_issues_list_count(4) + expect_filtered_search_input_empty + end + it 'filters issues by no label' do - input_filtered_search('label:none') + input_filtered_search('label!=none') expect_tokens([label_token('None', false)]) expect_issues_list_count(4) @@ -154,7 +170,18 @@ describe 'Filter issues', :js do end it 'filters issues by multiple labels' do - input_filtered_search("label:~#{bug_label.title} label:~#{caps_sensitive_label.title}") + input_filtered_search("label=~#{bug_label.title} label=~#{caps_sensitive_label.title}") + + expect_tokens([ + label_token(bug_label.title), + label_token(caps_sensitive_label.title) + ]) + expect_issues_list_count(1) + expect_filtered_search_input_empty + end + + it 'filters issues by multiple labels with not operator' do + input_filtered_search("label!=~#{bug_label.title} label=~#{caps_sensitive_label.title}") expect_tokens([ label_token(bug_label.title), @@ -169,22 +196,42 @@ describe 'Filter issues', :js do special_issue = create(:issue, title: "Issue with special character label", project: project) special_issue.labels << special_label - input_filtered_search("label:~#{special_label.title}") + input_filtered_search("label=~#{special_label.title}") expect_tokens([label_token(special_label.title)]) expect_issues_list_count(1) expect_filtered_search_input_empty end + it 'filters issues by label not containing special characters' do + special_label = create(:label, project: project, title: '!@#{$%^&*()-+[]<>?/:{}|\}') + special_issue = create(:issue, title: "Issue with special character label", project: project) + special_issue.labels << special_label + + input_filtered_search("label!=~#{special_label.title}") + + expect_tokens([label_token(special_label.title)]) + expect_issues_list_count(8) + expect_filtered_search_input_empty + end + it 'does not show issues for unused labels' do new_label = create(:label, project: project, title: 'new_label') - input_filtered_search("label:~#{new_label.title}") + input_filtered_search("label=~#{new_label.title}") expect_tokens([label_token(new_label.title)]) expect_no_issues_list expect_filtered_search_input_empty end + + it 'does show issues for bug label' do + input_filtered_search("label!=~#{bug_label.title}") + + expect_tokens([label_token(bug_label.title)]) + expect_issues_list_count(6) + expect_filtered_search_input_empty + end end context 'label with multiple words' do @@ -193,7 +240,7 @@ describe 'Filter issues', :js do special_multiple_issue = create(:issue, title: "Issue with special character multiple words label", project: project) special_multiple_issue.labels << special_multiple_label - input_filtered_search("label:~'#{special_multiple_label.title}'") + input_filtered_search("label=~'#{special_multiple_label.title}'") # Check for search results (which makes sure that the page has changed) expect_issues_list_count(1) @@ -205,7 +252,7 @@ describe 'Filter issues', :js do end it 'single quotes' do - input_filtered_search("label:~'#{multiple_words_label.title}'") + input_filtered_search("label=~'#{multiple_words_label.title}'") expect_issues_list_count(1) expect_tokens([label_token("\"#{multiple_words_label.title}\"")]) @@ -213,7 +260,7 @@ describe 'Filter issues', :js do end it 'double quotes' do - input_filtered_search("label:~\"#{multiple_words_label.title}\"") + input_filtered_search("label=~\"#{multiple_words_label.title}\"") expect_tokens([label_token("\"#{multiple_words_label.title}\"")]) expect_issues_list_count(1) @@ -225,7 +272,7 @@ describe 'Filter issues', :js do double_quotes_label_issue = create(:issue, title: "Issue with double quotes label", project: project) double_quotes_label_issue.labels << double_quotes_label - input_filtered_search("label:~'#{double_quotes_label.title}'") + input_filtered_search("label=~'#{double_quotes_label.title}'") expect_tokens([label_token("'#{double_quotes_label.title}'")]) expect_issues_list_count(1) @@ -237,7 +284,7 @@ describe 'Filter issues', :js do single_quotes_label_issue = create(:issue, title: "Issue with single quotes label", project: project) single_quotes_label_issue.labels << single_quotes_label - input_filtered_search("label:~\"#{single_quotes_label.title}\"") + input_filtered_search("label=~\"#{single_quotes_label.title}\"") expect_tokens([label_token("\"#{single_quotes_label.title}\"")]) expect_issues_list_count(1) @@ -249,7 +296,7 @@ describe 'Filter issues', :js do it 'filters issues by searched label, label2, author, assignee, milestone and text' do search_term = 'bug' - input_filtered_search("label:~#{bug_label.title} label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} milestone:%#{milestone.title} #{search_term}") + input_filtered_search("label=~#{bug_label.title} label=~#{caps_sensitive_label.title} author=@#{user.username} assignee=@#{user.username} milestone=%#{milestone.title} #{search_term}") wait_for_requests @@ -263,6 +310,24 @@ describe 'Filter issues', :js do expect_issues_list_count(1) expect_filtered_search_input(search_term) end + + it 'filters issues by searched label, label2, author, assignee, not included in a milestone' do + search_term = 'bug' + + input_filtered_search("label=~#{bug_label.title} label=~#{caps_sensitive_label.title} author=@#{user.username} assignee=@#{user.username} milestone!=%#{milestone.title} #{search_term}") + + wait_for_requests + + expect_tokens([ + label_token(bug_label.title), + label_token(caps_sensitive_label.title), + author_token(user.name), + assignee_token(user.name), + milestone_token(milestone.title, false, '!=') + ]) + expect_issues_list_count(0) + expect_filtered_search_input(search_term) + end end context 'issue label clicked' do @@ -279,7 +344,7 @@ describe 'Filter issues', :js do describe 'filter issues by milestone' do context 'only milestone' do it 'filters issues by searched milestone' do - input_filtered_search("milestone:%#{milestone.title}") + input_filtered_search("milestone=%#{milestone.title}") expect_tokens([milestone_token(milestone.title)]) expect_issues_list_count(5) @@ -287,53 +352,102 @@ describe 'Filter issues', :js do end it 'filters issues by no milestone' do - input_filtered_search("milestone:none") + input_filtered_search("milestone=none") expect_tokens([milestone_token('None', false)]) expect_issues_list_count(3) expect_filtered_search_input_empty end + it 'filters issues by negation of no milestone' do + input_filtered_search("milestone!=none ") + + expect_tokens([milestone_token('None', false, '!=')]) + expect_issues_list_count(5) + expect_filtered_search_input_empty + end + it 'filters issues by upcoming milestones' do create(:milestone, project: project, due_date: 1.month.from_now) do |future_milestone| create(:issue, project: project, milestone: future_milestone, author: user) end - input_filtered_search("milestone:upcoming") + input_filtered_search("milestone=upcoming") expect_tokens([milestone_token('Upcoming', false)]) expect_issues_list_count(1) expect_filtered_search_input_empty end + it 'filters issues by negation of upcoming milestones' do + create(:milestone, project: project, due_date: 1.month.from_now) do |future_milestone| + create(:issue, project: project, milestone: future_milestone, author: user) + end + + input_filtered_search("milestone!=upcoming") + + expect_tokens([milestone_token('Upcoming', false, '!=')]) + expect_issues_list_count(8) + expect_filtered_search_input_empty + end + it 'filters issues by started milestones' do - input_filtered_search("milestone:started") + input_filtered_search("milestone=started") expect_tokens([milestone_token('Started', false)]) expect_issues_list_count(5) expect_filtered_search_input_empty end + it 'filters issues by negation of started milestones' do + input_filtered_search("milestone!=started") + + expect_tokens([milestone_token('Started', false, '!=')]) + expect_issues_list_count(3) + expect_filtered_search_input_empty + end + it 'filters issues by milestone containing special characters' do special_milestone = create(:milestone, title: '!@\#{$%^&*()}', project: project) create(:issue, project: project, milestone: special_milestone) - input_filtered_search("milestone:%#{special_milestone.title}") + input_filtered_search("milestone=%#{special_milestone.title}") expect_tokens([milestone_token(special_milestone.title)]) expect_issues_list_count(1) expect_filtered_search_input_empty end + it 'filters issues by milestone not containing special characters' do + special_milestone = create(:milestone, title: '!@\#{$%^&*()}', project: project) + create(:issue, project: project, milestone: special_milestone) + + input_filtered_search("milestone!=%#{special_milestone.title}") + + expect_tokens([milestone_token(special_milestone.title, false, '!=')]) + expect_issues_list_count(8) + expect_filtered_search_input_empty + end + it 'does not show issues for unused milestones' do new_milestone = create(:milestone, title: 'new', project: project) - input_filtered_search("milestone:%#{new_milestone.title}") + input_filtered_search("milestone=%#{new_milestone.title}") expect_tokens([milestone_token(new_milestone.title)]) expect_no_issues_list expect_filtered_search_input_empty end + + it 'show issues for unused milestones' do + new_milestone = create(:milestone, title: 'new', project: project) + + input_filtered_search("milestone!=%#{new_milestone.title}") + + expect_tokens([milestone_token(new_milestone.title, false, '!=')]) + expect_issues_list_count(8) + expect_filtered_search_input_empty + end end end @@ -407,7 +521,7 @@ describe 'Filter issues', :js do context 'searched text with other filters' do it 'filters issues by searched text, author, text, assignee, text, label1, text, label2, text, milestone and text' do - input_filtered_search("bug author:@#{user.username} report label:~#{bug_label.title} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} foo") + input_filtered_search("bug author=@#{user.username} report label=~#{bug_label.title} label=~#{caps_sensitive_label.title} milestone=%#{milestone.title} foo") expect_issues_list_count(1) expect_filtered_search_input('bug report foo') @@ -481,7 +595,7 @@ describe 'Filter issues', :js do end it 'milestone dropdown loads milestones' do - input_filtered_search("milestone:", submit: false) + input_filtered_search("milestone=", submit: false) within('#js-dropdown-milestone') do expect(page).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1) @@ -489,7 +603,7 @@ describe 'Filter issues', :js do end it 'label dropdown load labels' do - input_filtered_search("label:", submit: false) + input_filtered_search("label=", submit: false) within('#js-dropdown-label') do expect(page).to have_selector('.filter-dropdown .filter-dropdown-item', count: 3) diff --git a/spec/features/issues/filtered_search/recent_searches_spec.rb b/spec/features/issues/filtered_search/recent_searches_spec.rb index c038281d825..e05c7aa3af5 100644 --- a/spec/features/issues/filtered_search/recent_searches_spec.rb +++ b/spec/features/issues/filtered_search/recent_searches_spec.rb @@ -41,8 +41,8 @@ describe 'Recent searches', :js do items = all('.filtered-search-history-dropdown-item', visible: false, count: 2) - expect(items[0].text).to eq('label: ~qux garply') - expect(items[1].text).to eq('label: ~foo bar') + expect(items[0].text).to eq('label: = ~qux garply') + expect(items[1].text).to eq('label: = ~foo bar') end it 'saved recent searches are restored last on the list' do diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb index e97314e02e6..ad994270218 100644 --- a/spec/features/issues/filtered_search/search_bar_spec.rb +++ b/spec/features/issues/filtered_search/search_bar_spec.rb @@ -34,7 +34,7 @@ describe 'Search bar', :js do it 'selects item' do filtered_search.native.send_keys(:down, :down, :enter) - expect_tokens([author_token]) + expect_tokens([{ name: 'Assignee' }]) expect_filtered_search_input_empty end end @@ -78,7 +78,7 @@ describe 'Search bar', :js do filtered_search.click original_size = page.all('#js-dropdown-hint .filter-dropdown .filter-dropdown-item').size - filtered_search.set('author') + filtered_search.set('autho') expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1) diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb index d1e976c3bca..2af2e096bcc 100644 --- a/spec/features/issues/filtered_search/visual_tokens_spec.rb +++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb @@ -36,8 +36,9 @@ describe 'Visual tokens', :js do describe 'editing a single token' do before do - input_filtered_search('author:@root assignee:none', submit: false) + input_filtered_search('author=@root assignee=none', submit: false) first('.tokens-container .filtered-search-token').click + wait_for_requests end it 'opens author dropdown' do @@ -76,8 +77,8 @@ describe 'Visual tokens', :js do describe 'editing multiple tokens' do before do - input_filtered_search('author:@root assignee:none', submit: false) - first('.tokens-container .filtered-search-token').double_click + input_filtered_search('author=@root assignee=none', submit: false) + first('.tokens-container .filtered-search-token').click end it 'opens author dropdown' do @@ -85,27 +86,33 @@ describe 'Visual tokens', :js do end it 'opens assignee dropdown' do - find('.tokens-container .filtered-search-token', text: 'Assignee').double_click + find('.tokens-container .filtered-search-token', text: 'Assignee').click expect(page).to have_css('#js-dropdown-assignee', visible: true) end end describe 'editing a search term while editing another filter token' do before do - input_filtered_search('author assignee:', submit: false) - first('.tokens-container .filtered-search-term').double_click + input_filtered_search('foo assignee=', submit: false) + first('.tokens-container .filtered-search-term').click end it 'opens author dropdown' do - find('#js-dropdown-hint .filter-dropdown .filter-dropdown-item', text: 'author').click + find('#js-dropdown-hint .filter-dropdown .filter-dropdown-item', text: 'Author').click + + expect(page).to have_css('#js-dropdown-operator', visible: true) + expect(page).to have_css('#js-dropdown-author', visible: false) + + find('#js-dropdown-operator .filter-dropdown .filter-dropdown-item[data-value="="]').click + expect(page).to have_css('#js-dropdown-operator', visible: false) expect(page).to have_css('#js-dropdown-author', visible: true) end end describe 'add new token after editing existing token' do before do - input_filtered_search('author:@root assignee:none', submit: false) + input_filtered_search('author=@root assignee=none', submit: false) first('.tokens-container .filtered-search-token').double_click filtered_search.send_keys(' ') end @@ -116,7 +123,7 @@ describe 'Visual tokens', :js do end it 'opens token dropdown' do - filtered_search.send_keys('author:') + filtered_search.send_keys('author=') expect(page).to have_css('#js-dropdown-author', visible: true) end @@ -124,7 +131,7 @@ describe 'Visual tokens', :js do describe 'visual tokens' do it 'creates visual token' do - filtered_search.send_keys('author:@thomas ') + filtered_search.send_keys('author=@thomas ') token = page.all('.tokens-container .filtered-search-token')[1] expect(token.find('.name').text).to eq('Author') @@ -133,7 +140,7 @@ describe 'Visual tokens', :js do end it 'does not tokenize incomplete token' do - filtered_search.send_keys('author:') + filtered_search.send_keys('author=') find('body').click token = page.all('.tokens-container .js-visual-token')[1] @@ -145,7 +152,7 @@ describe 'Visual tokens', :js do describe 'search using incomplete visual tokens' do before do - input_filtered_search('author:@root assignee:none', extra_space: false) + input_filtered_search('author=@root assignee=none', extra_space: false) end it 'tokenizes the search term to complete visual token' do diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb index b7a45905845..c1a2e22a0c2 100644 --- a/spec/features/labels_hierarchy_spec.rb +++ b/spec/features/labels_hierarchy_spec.rb @@ -70,7 +70,7 @@ describe 'Labels Hierarchy', :js do end it 'does not filter by descendant group labels' do - filtered_search.set("label:") + filtered_search.set("label=") wait_for_requests @@ -134,7 +134,7 @@ describe 'Labels Hierarchy', :js do end it 'does not filter by descendant group project labels' do - filtered_search.set("label:") + filtered_search.set("label=") wait_for_requests @@ -227,7 +227,7 @@ describe 'Labels Hierarchy', :js do it_behaves_like 'filtering by ancestor labels for projects' it 'does not filter by descendant group labels' do - filtered_search.set("label:") + filtered_search.set("label=") wait_for_requests diff --git a/spec/features/merge_requests/filters_generic_behavior_spec.rb b/spec/features/merge_requests/filters_generic_behavior_spec.rb index 58aad1b7e91..c3400acae4f 100644 --- a/spec/features/merge_requests/filters_generic_behavior_spec.rb +++ b/spec/features/merge_requests/filters_generic_behavior_spec.rb @@ -23,7 +23,7 @@ describe 'Merge Requests > Filters generic behavior', :js do context 'when filtered by a label' do before do - input_filtered_search('label:~bug') + input_filtered_search('label=~bug') end describe 'state tabs' do diff --git a/spec/features/merge_requests/user_filters_by_assignees_spec.rb b/spec/features/merge_requests/user_filters_by_assignees_spec.rb index 00bd8455ae1..3abee3b656a 100644 --- a/spec/features/merge_requests/user_filters_by_assignees_spec.rb +++ b/spec/features/merge_requests/user_filters_by_assignees_spec.rb @@ -18,7 +18,7 @@ describe 'Merge Requests > User filters by assignees', :js do context 'filtering by assignee:none' do it 'applies the filter' do - input_filtered_search('assignee:none') + input_filtered_search('assignee=none') expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).not_to have_content 'Bugfix1' @@ -26,9 +26,9 @@ describe 'Merge Requests > User filters by assignees', :js do end end - context 'filtering by assignee:@username' do + context 'filtering by assignee=@username' do it 'applies the filter' do - input_filtered_search("assignee:@#{user.username}") + input_filtered_search("assignee=@#{user.username}") expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_content 'Bugfix1' diff --git a/spec/features/merge_requests/user_filters_by_labels_spec.rb b/spec/features/merge_requests/user_filters_by_labels_spec.rb index fd2b4b23f96..7a80ebe9be3 100644 --- a/spec/features/merge_requests/user_filters_by_labels_spec.rb +++ b/spec/features/merge_requests/user_filters_by_labels_spec.rb @@ -22,7 +22,7 @@ describe 'Merge Requests > User filters by labels', :js do context 'filtering by label:none' do it 'applies the filter' do - input_filtered_search('label:none') + input_filtered_search('label=none') expect(page).to have_issuable_counts(open: 0, closed: 0, all: 0) expect(page).not_to have_content 'Bugfix1' @@ -32,7 +32,7 @@ describe 'Merge Requests > User filters by labels', :js do context 'filtering by label:~enhancement' do it 'applies the filter' do - input_filtered_search('label:~enhancement') + input_filtered_search('label=~enhancement') expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_content 'Bugfix2' @@ -42,7 +42,7 @@ describe 'Merge Requests > User filters by labels', :js do context 'filtering by label:~enhancement and label:~bug' do it 'applies the filters' do - input_filtered_search('label:~bug label:~enhancement') + input_filtered_search('label=~bug label=~enhancement') expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_content 'Bugfix2' diff --git a/spec/features/merge_requests/user_filters_by_milestones_spec.rb b/spec/features/merge_requests/user_filters_by_milestones_spec.rb index e0ee69d7a5b..8cb686e191e 100644 --- a/spec/features/merge_requests/user_filters_by_milestones_spec.rb +++ b/spec/features/merge_requests/user_filters_by_milestones_spec.rb @@ -18,14 +18,14 @@ describe 'Merge Requests > User filters by milestones', :js do end it 'filters by no milestone' do - input_filtered_search('milestone:none') + input_filtered_search('milestone=none') expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_css('.merge-request', count: 1) end it 'filters by a specific milestone' do - input_filtered_search("milestone:%'#{milestone.title}'") + input_filtered_search("milestone=%'#{milestone.title}'") expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_css('.merge-request', count: 1) @@ -33,7 +33,7 @@ describe 'Merge Requests > User filters by milestones', :js do describe 'filters by upcoming milestone' do it 'does not show merge requests with no expiry' do - input_filtered_search('milestone:upcoming') + input_filtered_search('milestone=upcoming') expect(page).to have_issuable_counts(open: 0, closed: 0, all: 0) expect(page).to have_css('.merge-request', count: 0) @@ -43,7 +43,7 @@ describe 'Merge Requests > User filters by milestones', :js do let(:milestone) { create(:milestone, project: project, due_date: Date.tomorrow) } it 'shows merge requests' do - input_filtered_search('milestone:upcoming') + input_filtered_search('milestone=upcoming') expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_css('.merge-request', count: 1) @@ -54,7 +54,7 @@ describe 'Merge Requests > User filters by milestones', :js do let(:milestone) { create(:milestone, project: project, due_date: Date.yesterday) } it 'does not show any merge requests' do - input_filtered_search('milestone:upcoming') + input_filtered_search('milestone=upcoming') expect(page).to have_issuable_counts(open: 0, closed: 0, all: 0) expect(page).to have_css('.merge-request', count: 0) diff --git a/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb b/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb index bc6e2ac5132..5c9d53778d2 100644 --- a/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb +++ b/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb @@ -20,7 +20,7 @@ describe 'Merge requests > User filters by multiple criteria', :js do describe 'filtering by label:~"Won\'t fix" and assignee:~bug' do it 'applies the filters' do - input_filtered_search("label:~\"Won't fix\" assignee:@#{user.username}") + input_filtered_search("label=~\"Won't fix\" assignee=@#{user.username}") expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_content 'Bugfix2' @@ -30,7 +30,7 @@ describe 'Merge requests > User filters by multiple criteria', :js do describe 'filtering by text, author, assignee, milestone, and label' do it 'filters by text, author, assignee, milestone, and label' do - input_filtered_search_keys("author:@#{user.username} assignee:@#{user.username} milestone:%\"v1.1\" label:~\"Won't fix\" Bug") + input_filtered_search_keys("author=@#{user.username} assignee=@#{user.username} milestone=%\"v1.1\" label=~\"Won't fix\" Bug") expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_content 'Bugfix2' diff --git a/spec/features/merge_requests/user_filters_by_target_branch_spec.rb b/spec/features/merge_requests/user_filters_by_target_branch_spec.rb index 0d03c5eae31..faff7de729d 100644 --- a/spec/features/merge_requests/user_filters_by_target_branch_spec.rb +++ b/spec/features/merge_requests/user_filters_by_target_branch_spec.rb @@ -17,7 +17,7 @@ describe 'Merge Requests > User filters by target branch', :js do context 'filtering by target-branch:master' do it 'applies the filter' do - input_filtered_search('target-branch:master') + input_filtered_search('target-branch=master') expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_content mr1.title @@ -27,7 +27,7 @@ describe 'Merge Requests > User filters by target branch', :js do context 'filtering by target-branch:merged-target' do it 'applies the filter' do - input_filtered_search('target-branch:merged-target') + input_filtered_search('target-branch=merged-target') expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).not_to have_content mr1.title @@ -37,7 +37,7 @@ describe 'Merge Requests > User filters by target branch', :js do context 'filtering by target-branch:feature' do it 'applies the filter' do - input_filtered_search('target-branch:feature') + input_filtered_search('target-branch=feature') expect(page).to have_issuable_counts(open: 0, closed: 0, all: 0) expect(page).not_to have_content mr1.title diff --git a/spec/frontend/error_tracking/components/error_tracking_list_spec.js b/spec/frontend/error_tracking/components/error_tracking_list_spec.js index f00ba884a6c..66104724163 100644 --- a/spec/frontend/error_tracking/components/error_tracking_list_spec.js +++ b/spec/frontend/error_tracking/components/error_tracking_list_spec.js @@ -1,15 +1,7 @@ -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { createLocalVue, mount } from '@vue/test-utils'; import Vuex from 'vuex'; -import { - GlEmptyState, - GlLoadingIcon, - GlTable, - GlLink, - GlFormInput, - GlDropdown, - GlDropdownItem, - GlPagination, -} from '@gitlab/ui'; +import { GlEmptyState, GlLoadingIcon, GlFormInput, GlPagination } from '@gitlab/ui'; +import stubChildren from 'helpers/stub_children'; import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue'; import errorsList from './list_mock.json'; @@ -32,19 +24,12 @@ describe('ErrorTrackingList', () => { function mountComponent({ errorTrackingEnabled = true, userCanEnableErrorTracking = true, - sync = true, - stubs = { - 'gl-link': GlLink, - 'gl-table': GlTable, - 'gl-pagination': GlPagination, - 'gl-dropdown': GlDropdown, - 'gl-dropdown-item': GlDropdownItem, - }, + stubs = {}, } = {}) { - wrapper = shallowMount(ErrorTrackingList, { + wrapper = mount(ErrorTrackingList, { localVue, store, - sync, + sync: false, propsData: { indexPath: '/path', enableErrorTrackingLink: '/link', @@ -52,7 +37,10 @@ describe('ErrorTrackingList', () => { errorTrackingEnabled, illustrationPath: 'illustration/path', }, - stubs, + stubs: { + ...stubChildren(ErrorTrackingList), + ...stubs, + }, data() { return { errorSearchQuery: 'search' }; }, @@ -122,7 +110,14 @@ describe('ErrorTrackingList', () => { beforeEach(() => { store.state.list.loading = false; store.state.list.errors = errorsList; - mountComponent(); + mountComponent({ + stubs: { + GlTable: false, + GlDropdown: false, + GlDropdownItem: false, + GlLink: false, + }, + }); }); it('shows table', () => { @@ -173,7 +168,13 @@ describe('ErrorTrackingList', () => { store.state.list.loading = false; store.state.list.errors = []; - mountComponent(); + mountComponent({ + stubs: { + GlTable: false, + GlDropdown: false, + GlDropdownItem: false, + }, + }); }); it('shows empty table', () => { @@ -187,7 +188,7 @@ describe('ErrorTrackingList', () => { }); it('restarts polling', () => { - findRefreshLink().trigger('click'); + findRefreshLink().vm.$emit('click'); expect(actions.restartPolling).toHaveBeenCalled(); }); }); @@ -211,8 +212,8 @@ describe('ErrorTrackingList', () => { errorTrackingEnabled: false, userCanEnableErrorTracking: false, stubs: { - 'gl-link': GlLink, - 'gl-empty-state': GlEmptyState, + GlLink: false, + GlEmptyState: false, }, }); }); @@ -226,7 +227,12 @@ describe('ErrorTrackingList', () => { describe('recent searches', () => { beforeEach(() => { - mountComponent(); + mountComponent({ + stubs: { + GlDropdown: false, + GlDropdownItem: false, + }, + }); }); it('shows empty message', () => { @@ -238,11 +244,12 @@ describe('ErrorTrackingList', () => { it('shows items', () => { store.state.list.recentSearches = ['great', 'search']; - const dropdownItems = wrapper.findAll('.filtered-search-box li'); - - expect(dropdownItems.length).toBe(3); - expect(dropdownItems.at(0).text()).toBe('great'); - expect(dropdownItems.at(1).text()).toBe('search'); + return wrapper.vm.$nextTick().then(() => { + const dropdownItems = wrapper.findAll('.filtered-search-box li'); + expect(dropdownItems.length).toBe(3); + expect(dropdownItems.at(0).text()).toBe('great'); + expect(dropdownItems.at(1).text()).toBe('search'); + }); }); describe('clear', () => { @@ -257,16 +264,20 @@ describe('ErrorTrackingList', () => { it('is visible when list has items', () => { store.state.list.recentSearches = ['some', 'searches']; - expect(clearRecentButton().exists()).toBe(true); - expect(clearRecentButton().text()).toBe('Clear recent searches'); + return wrapper.vm.$nextTick().then(() => { + expect(clearRecentButton().exists()).toBe(true); + expect(clearRecentButton().text()).toBe('Clear recent searches'); + }); }); it('clears items on click', () => { store.state.list.recentSearches = ['some', 'searches']; - clearRecentButton().vm.$emit('click'); + return wrapper.vm.$nextTick().then(() => { + clearRecentButton().vm.$emit('click'); - expect(actions.clearRecentSearches).toHaveBeenCalledTimes(1); + expect(actions.clearRecentSearches).toHaveBeenCalledTimes(1); + }); }); }); }); @@ -287,7 +298,11 @@ describe('ErrorTrackingList', () => { describe('and the user is on the first page', () => { beforeEach(() => { store.state.list.loading = false; - mountComponent({ sync: false }); + mountComponent({ + stubs: { + GlPagination: false, + }, + }); }); it('shows a disabled Prev button', () => { @@ -299,8 +314,14 @@ describe('ErrorTrackingList', () => { describe('and the previous button is clicked', () => { beforeEach(() => { store.state.list.loading = false; - mountComponent({ sync: false }); + mountComponent({ + stubs: { + GlTable: false, + GlPagination: false, + }, + }); wrapper.setData({ pageValue: 2 }); + return wrapper.vm.$nextTick(); }); it('fetches the previous page of results', () => { @@ -318,7 +339,7 @@ describe('ErrorTrackingList', () => { describe('and the next page button is clicked', () => { beforeEach(() => { store.state.list.loading = false; - mountComponent({ sync: false }); + mountComponent(); }); it('fetches the next page of results', () => { diff --git a/spec/frontend/filtered_search/filtered_search_token_keys_spec.js b/spec/frontend/filtered_search/filtered_search_token_keys_spec.js index d1fea18dea8..f24d2b118c2 100644 --- a/spec/frontend/filtered_search/filtered_search_token_keys_spec.js +++ b/spec/frontend/filtered_search/filtered_search_token_keys_spec.js @@ -124,6 +124,7 @@ describe('Filtered Search Token Keys', () => { const condition = new FilteredSearchTokenKeys([], [], conditions).searchByConditionKeyValue( null, null, + null, ); expect(condition).toBeNull(); @@ -132,6 +133,7 @@ describe('Filtered Search Token Keys', () => { it('should return condition when found by tokenKey and value', () => { const result = new FilteredSearchTokenKeys([], [], conditions).searchByConditionKeyValue( conditions[0].tokenKey, + conditions[0].operator, conditions[0].value, ); diff --git a/spec/frontend/helpers/stub_children.js b/spec/frontend/helpers/stub_children.js new file mode 100644 index 00000000000..91171eb3d8c --- /dev/null +++ b/spec/frontend/helpers/stub_children.js @@ -0,0 +1,3 @@ +export default function stubChildren(Component) { + return Object.fromEntries(Object.keys(Component.components).map(c => [c, true])); +} diff --git a/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js b/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js index c88a182660d..f1b4c370532 100644 --- a/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js +++ b/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js @@ -1,4 +1,4 @@ -import { shallowMount } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import UserModalManager from '~/pages/admin/users/components/user_modal_manager.vue'; import ModalStub from './stubs/modal_stub'; @@ -22,17 +22,13 @@ describe('Users admin page Modal Manager', () => { let wrapper; const createComponent = (props = {}) => { - wrapper = shallowMount(UserModalManager, { + wrapper = mount(UserModalManager, { propsData: { actionModals, modalConfiguration, csrfToken: 'dummyCSRF', ...props, }, - stubs: { - dummyComponent1: true, - dummyComponent2: true, - }, sync: false, }); }; diff --git a/spec/javascripts/droplab/drop_down_spec.js b/spec/javascripts/droplab/drop_down_spec.js index 18ab03653f4..22346c10547 100644 --- a/spec/javascripts/droplab/drop_down_spec.js +++ b/spec/javascripts/droplab/drop_down_spec.js @@ -398,14 +398,21 @@ describe('DropLab DropDown', function() { describe('render', function() { beforeEach(function() { - this.list = { querySelector: () => {}, dispatchEvent: () => {} }; - this.dropdown = { renderChildren: () => {}, list: this.list }; this.renderableList = {}; + this.list = { + querySelector: q => { + if (q === '.filter-dropdown-loading') { + return false; + } + return this.renderableList; + }, + dispatchEvent: () => {}, + }; + this.dropdown = { renderChildren: () => {}, list: this.list }; this.data = [0, 1]; this.customEvent = {}; spyOn(this.dropdown, 'renderChildren').and.callFake(data => data); - spyOn(this.list, 'querySelector').and.returnValue(this.renderableList); spyOn(this.list, 'dispatchEvent'); spyOn(this.data, 'map').and.callThrough(); spyOn(window, 'CustomEvent').and.returnValue(this.customEvent); diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js index 62d1bd69635..6eda4f391a4 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js @@ -222,7 +222,7 @@ describe('Dropdown Utils', () => { hasAttribute: () => false, }; - DropdownUtils.setDataValueIfSelected(null, selected); + DropdownUtils.setDataValueIfSelected(null, '=', selected); expect(FilteredSearchDropdownManager.addWordToInput.calls.count()).toEqual(1); }); @@ -233,9 +233,11 @@ describe('Dropdown Utils', () => { hasAttribute: () => false, }; - const result = DropdownUtils.setDataValueIfSelected(null, selected); + const result = DropdownUtils.setDataValueIfSelected(null, '=', selected); + const result2 = DropdownUtils.setDataValueIfSelected(null, '!=', selected); expect(result).toBe(true); + expect(result2).toBe(true); }); it('returns false when dataValue does not exist', () => { @@ -243,9 +245,11 @@ describe('Dropdown Utils', () => { getAttribute: () => null, }; - const result = DropdownUtils.setDataValueIfSelected(null, selected); + const result = DropdownUtils.setDataValueIfSelected(null, '=', selected); + const result2 = DropdownUtils.setDataValueIfSelected(null, '!=', selected); expect(result).toBe(false); + expect(result2).toBe(false); }); }); @@ -349,7 +353,7 @@ describe('Dropdown Utils', () => { beforeEach(() => { loadFixtures(issueListFixture); - authorToken = FilteredSearchSpecHelper.createFilterVisualToken('author', '@user'); + authorToken = FilteredSearchSpecHelper.createFilterVisualToken('author', '=', '@user'); const searchTermToken = FilteredSearchSpecHelper.createSearchVisualToken('search term'); const tokensContainer = document.querySelector('.tokens-container'); @@ -364,7 +368,7 @@ describe('Dropdown Utils', () => { const searchQuery = DropdownUtils.getSearchQuery(); - expect(searchQuery).toBe(' search term author:original dance'); + expect(searchQuery).toBe(' search term author:=original dance'); }); }); }); diff --git a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js index 8c5a0961a02..853f6b3b7b8 100644 --- a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js @@ -27,7 +27,7 @@ describe('Filtered Search Dropdown Manager', () => { describe('input has no existing value', () => { it('should add just tokenName', () => { - FilteredSearchDropdownManager.addWordToInput('milestone'); + FilteredSearchDropdownManager.addWordToInput({ tokenName: 'milestone' }); const token = document.querySelector('.tokens-container .js-visual-token'); @@ -36,8 +36,8 @@ describe('Filtered Search Dropdown Manager', () => { expect(getInputValue()).toBe(''); }); - it('should add tokenName and tokenValue', () => { - FilteredSearchDropdownManager.addWordToInput('label'); + it('should add tokenName, tokenOperator, and tokenValue', () => { + FilteredSearchDropdownManager.addWordToInput({ tokenName: 'label' }); let token = document.querySelector('.tokens-container .js-visual-token'); @@ -45,13 +45,27 @@ describe('Filtered Search Dropdown Manager', () => { expect(token.querySelector('.name').innerText).toBe('label'); expect(getInputValue()).toBe(''); - FilteredSearchDropdownManager.addWordToInput('label', 'none'); + FilteredSearchDropdownManager.addWordToInput({ tokenName: 'label', tokenOperator: '=' }); + + token = document.querySelector('.tokens-container .js-visual-token'); + + expect(token.classList.contains('filtered-search-token')).toEqual(true); + expect(token.querySelector('.name').innerText).toBe('label'); + expect(token.querySelector('.operator').innerText).toBe('='); + expect(getInputValue()).toBe(''); + + FilteredSearchDropdownManager.addWordToInput({ + tokenName: 'label', + tokenOperator: '=', + tokenValue: 'none', + }); // We have to get that reference again // Because FilteredSearchDropdownManager deletes the previous token token = document.querySelector('.tokens-container .js-visual-token'); expect(token.classList.contains('filtered-search-token')).toEqual(true); expect(token.querySelector('.name').innerText).toBe('label'); + expect(token.querySelector('.operator').innerText).toBe('='); expect(token.querySelector('.value').innerText).toBe('none'); expect(getInputValue()).toBe(''); }); @@ -60,7 +74,7 @@ describe('Filtered Search Dropdown Manager', () => { describe('input has existing value', () => { it('should be able to just add tokenName', () => { setInputValue('a'); - FilteredSearchDropdownManager.addWordToInput('author'); + FilteredSearchDropdownManager.addWordToInput({ tokenName: 'author' }); const token = document.querySelector('.tokens-container .js-visual-token'); @@ -70,29 +84,40 @@ describe('Filtered Search Dropdown Manager', () => { }); it('should replace tokenValue', () => { - FilteredSearchDropdownManager.addWordToInput('author'); + FilteredSearchDropdownManager.addWordToInput({ tokenName: 'author' }); + FilteredSearchDropdownManager.addWordToInput({ tokenName: 'author', tokenOperator: '=' }); setInputValue('roo'); - FilteredSearchDropdownManager.addWordToInput(null, '@root'); + FilteredSearchDropdownManager.addWordToInput({ + tokenName: null, + tokenOperator: '=', + tokenValue: '@root', + }); const token = document.querySelector('.tokens-container .js-visual-token'); expect(token.classList.contains('filtered-search-token')).toEqual(true); expect(token.querySelector('.name').innerText).toBe('author'); + expect(token.querySelector('.operator').innerText).toBe('='); expect(token.querySelector('.value').innerText).toBe('@root'); expect(getInputValue()).toBe(''); }); it('should add tokenValues containing spaces', () => { - FilteredSearchDropdownManager.addWordToInput('label'); + FilteredSearchDropdownManager.addWordToInput({ tokenName: 'label' }); setInputValue('"test '); - FilteredSearchDropdownManager.addWordToInput('label', '~\'"test me"\''); + FilteredSearchDropdownManager.addWordToInput({ + tokenName: 'label', + tokenOperator: '=', + tokenValue: '~\'"test me"\'', + }); const token = document.querySelector('.tokens-container .js-visual-token'); expect(token.classList.contains('filtered-search-token')).toEqual(true); expect(token.querySelector('.name').innerText).toBe('label'); + expect(token.querySelector('.operator').innerText).toBe('='); expect(token.querySelector('.value').innerText).toBe('~\'"test me"\''); expect(getInputValue()).toBe(''); }); diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js index e076120f5cc..e5d1d1d690e 100644 --- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js @@ -201,8 +201,8 @@ describe('Filtered Search Manager', function() { it('removes duplicated tokens', done => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')} - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '=', '~bug')} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '=', '~bug')} `); spyOnDependency(FilteredSearchManager, 'visitUrl').and.callFake(url => { @@ -234,7 +234,7 @@ describe('Filtered Search Manager', function() { it('should not render placeholder when there are tokens and no input', () => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug'), + FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '=', '~bug'), ); const event = new Event('input'); @@ -252,7 +252,7 @@ describe('Filtered Search Manager', function() { describe('tokens and no input', () => { beforeEach(() => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug'), + FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '=', '~bug'), ); }); @@ -306,7 +306,7 @@ describe('Filtered Search Manager', function() { it('removes token even when it is already selected', () => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true), + FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', '=', 'none', true), ); tokensContainer.querySelector('.js-visual-token .remove-token').click(); @@ -319,7 +319,7 @@ describe('Filtered Search Manager', function() { spyOn(FilteredSearchManager.prototype, 'removeSelectedToken').and.callThrough(); tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none'), + FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', '=', 'none'), ); tokensContainer.querySelector('.js-visual-token .remove-token').click(); }); @@ -338,7 +338,7 @@ describe('Filtered Search Manager', function() { beforeEach(() => { initializeManager(); tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true), + FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', '=', 'none', true), ); }); @@ -424,7 +424,7 @@ describe('Filtered Search Manager', function() { }); it('Clicking the "x" clear button, clears the input', () => { - const inputValue = 'label:~bug '; + const inputValue = 'label:=~bug'; manager.filteredSearchInput.value = inputValue; manager.filteredSearchInput.dispatchEvent(new Event('input')); diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js index 0ee13faf841..fda078bd41c 100644 --- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js @@ -6,9 +6,10 @@ describe('Filtered Search Visual Tokens', () => { const findElements = tokenElement => { const tokenNameElement = tokenElement.querySelector('.name'); + const tokenOperatorElement = tokenElement.querySelector('.operator'); const tokenValueContainer = tokenElement.querySelector('.value-container'); const tokenValueElement = tokenValueContainer.querySelector('.value'); - return { tokenNameElement, tokenValueContainer, tokenValueElement }; + return { tokenNameElement, tokenOperatorElement, tokenValueContainer, tokenValueElement }; }; let tokensContainer; @@ -23,8 +24,8 @@ describe('Filtered Search Visual Tokens', () => { `); tokensContainer = document.querySelector('.tokens-container'); - authorToken = FilteredSearchSpecHelper.createFilterVisualToken('author', '@user'); - bugLabelToken = FilteredSearchSpecHelper.createFilterVisualToken('label', '~bug'); + authorToken = FilteredSearchSpecHelper.createFilterVisualToken('author', '=', '@user'); + bugLabelToken = FilteredSearchSpecHelper.createFilterVisualToken('label', '=', '~bug'); }); describe('getLastVisualTokenBeforeInput', () => { @@ -62,7 +63,7 @@ describe('Filtered Search Visual Tokens', () => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` ${bugLabelToken.outerHTML} ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')} - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '@root')} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '=', '@root')} `); const { lastVisualToken, isLastVisualTokenValid } = subject.getLastVisualTokenBeforeInput(); @@ -92,7 +93,7 @@ describe('Filtered Search Visual Tokens', () => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` ${bugLabelToken.outerHTML} ${FilteredSearchSpecHelper.createInputHTML()} - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '@root')} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '=', '@root')} `); const { lastVisualToken, isLastVisualTokenValid } = subject.getLastVisualTokenBeforeInput(); @@ -105,7 +106,7 @@ describe('Filtered Search Visual Tokens', () => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` ${FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('label')} ${FilteredSearchSpecHelper.createInputHTML()} - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '@root')} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '=', '@root')} `); const { lastVisualToken, isLastVisualTokenValid } = subject.getLastVisualTokenBeforeInput(); @@ -150,8 +151,8 @@ describe('Filtered Search Visual Tokens', () => { it('removes the selected class from buttons', () => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '@author')} - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', '%123', true)} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '=', '@author')} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', '=', '%123', true)} `); const selected = tokensContainer.querySelector('.js-visual-token .selected'); @@ -169,7 +170,7 @@ describe('Filtered Search Visual Tokens', () => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` ${bugLabelToken.outerHTML} ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')} - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~awesome')} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '=', '~awesome')} `); }); @@ -206,7 +207,7 @@ describe('Filtered Search Visual Tokens', () => { describe('removeSelectedToken', () => { it('does not remove when there are no selected tokens', () => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none'), + FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', '=', 'none'), ); expect(tokensContainer.querySelector('.js-visual-token .selectable')).not.toEqual(null); @@ -218,7 +219,7 @@ describe('Filtered Search Visual Tokens', () => { it('removes selected token', () => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true), + FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', '=', 'none', true), ); expect(tokensContainer.querySelector('.js-visual-token .selectable')).not.toEqual(null); @@ -281,16 +282,22 @@ describe('Filtered Search Visual Tokens', () => { describe('addVisualTokenElement', () => { it('renders search visual tokens', () => { - subject.addVisualTokenElement('search term', null, { isSearchTerm: true }); + subject.addVisualTokenElement({ + name: 'search term', + operator: '=', + value: null, + options: { isSearchTerm: true }, + }); const token = tokensContainer.querySelector('.js-visual-token'); expect(token.classList.contains('filtered-search-term')).toEqual(true); expect(token.querySelector('.name').innerText).toEqual('search term'); + expect(token.querySelector('.operator').innerText).toEqual('='); expect(token.querySelector('.value')).toEqual(null); }); it('renders filter visual token name', () => { - subject.addVisualTokenElement('milestone'); + subject.addVisualTokenElement({ name: 'milestone' }); const token = tokensContainer.querySelector('.js-visual-token'); expect(token.classList.contains('search-token-milestone')).toEqual(true); @@ -299,22 +306,23 @@ describe('Filtered Search Visual Tokens', () => { expect(token.querySelector('.value')).toEqual(null); }); - it('renders filter visual token name and value', () => { - subject.addVisualTokenElement('label', 'Frontend'); + it('renders filter visual token name, operator, and value', () => { + subject.addVisualTokenElement({ name: 'label', operator: '!=', value: 'Frontend' }); const token = tokensContainer.querySelector('.js-visual-token'); expect(token.classList.contains('search-token-label')).toEqual(true); expect(token.classList.contains('filtered-search-token')).toEqual(true); expect(token.querySelector('.name').innerText).toEqual('label'); + expect(token.querySelector('.operator').innerText).toEqual('!='); expect(token.querySelector('.value').innerText).toEqual('Frontend'); }); it('inserts visual token before input', () => { tokensContainer.appendChild( - FilteredSearchSpecHelper.createFilterVisualToken('assignee', '@root'), + FilteredSearchSpecHelper.createFilterVisualToken('assignee', '=', '@root'), ); - subject.addVisualTokenElement('label', 'Frontend'); + subject.addVisualTokenElement({ name: 'label', operator: '!=', value: 'Frontend' }); const tokens = tokensContainer.querySelectorAll('.js-visual-token'); const labelToken = tokens[0]; const assigneeToken = tokens[1]; @@ -323,18 +331,20 @@ describe('Filtered Search Visual Tokens', () => { expect(labelToken.classList.contains('filtered-search-token')).toEqual(true); expect(labelToken.querySelector('.name').innerText).toEqual('label'); expect(labelToken.querySelector('.value').innerText).toEqual('Frontend'); + expect(labelToken.querySelector('.operator').innerText).toEqual('!='); expect(assigneeToken.classList.contains('search-token-assignee')).toEqual(true); expect(assigneeToken.classList.contains('filtered-search-token')).toEqual(true); expect(assigneeToken.querySelector('.name').innerText).toEqual('assignee'); expect(assigneeToken.querySelector('.value').innerText).toEqual('@root'); + expect(assigneeToken.querySelector('.operator').innerText).toEqual('='); }); }); describe('addValueToPreviousVisualTokenElement', () => { it('does not add when previous visual token element has no value', () => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '@root'), + FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '=', '@root'), ); const original = tokensContainer.innerHTML; @@ -345,7 +355,7 @@ describe('Filtered Search Visual Tokens', () => { it('does not add when previous visual token element is a search', () => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '@root')} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '=', '@root')} ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')} `); @@ -357,7 +367,7 @@ describe('Filtered Search Visual Tokens', () => { it('adds value to previous visual filter token', () => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('label'), + FilteredSearchSpecHelper.createNameOperatorFilterVisualTokenHTML('label', '='), ); const original = tokensContainer.innerHTML; @@ -377,25 +387,28 @@ describe('Filtered Search Visual Tokens', () => { expect(token.classList.contains('filtered-search-token')).toEqual(true); expect(token.querySelector('.name').innerText).toEqual('milestone'); + expect(token.querySelector('.operator')).toEqual(null); expect(token.querySelector('.value')).toEqual(null); }); it('creates visual token with just tokenValue', () => { - subject.addFilterVisualToken('milestone'); + subject.addFilterVisualToken('milestone', '='); subject.addFilterVisualToken('%8.17'); const token = tokensContainer.querySelector('.js-visual-token'); expect(token.classList.contains('filtered-search-token')).toEqual(true); expect(token.querySelector('.name').innerText).toEqual('milestone'); + expect(token.querySelector('.operator').innerText).toEqual('='); expect(token.querySelector('.value').innerText).toEqual('%8.17'); }); it('creates full visual token', () => { - subject.addFilterVisualToken('assignee', '@john'); + subject.addFilterVisualToken('assignee', '=', '@john'); const token = tokensContainer.querySelector('.js-visual-token'); expect(token.classList.contains('filtered-search-token')).toEqual(true); expect(token.querySelector('.name').innerText).toEqual('assignee'); + expect(token.querySelector('.operator').innerText).toEqual('='); expect(token.querySelector('.value').innerText).toEqual('@john'); }); }); @@ -412,7 +425,7 @@ describe('Filtered Search Visual Tokens', () => { it('appends to previous search visual token if previous token was a search token', () => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '@root')} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '=', '@root')} ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')} `); @@ -467,7 +480,11 @@ describe('Filtered Search Visual Tokens', () => { describe('removeLastTokenPartial', () => { it('should remove the last token value if it exists', () => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~"Community Contribution"'), + FilteredSearchSpecHelper.createFilterVisualTokenHTML( + 'label', + '=', + '~"Community Contribution"', + ), ); expect(tokensContainer.querySelector('.js-visual-token .value')).not.toEqual(null); @@ -507,7 +524,7 @@ describe('Filtered Search Visual Tokens', () => { it('adds search visual token if previous visual token is valid', () => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('assignee', 'none'), + FilteredSearchSpecHelper.createFilterVisualTokenHTML('assignee', '=', 'none'), ); const input = document.querySelector('.filtered-search'); @@ -523,7 +540,7 @@ describe('Filtered Search Visual Tokens', () => { it('adds value to previous visual token element if previous visual token is invalid', () => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('assignee'), + FilteredSearchSpecHelper.createNameOperatorFilterVisualTokenHTML('assignee', '='), ); const input = document.querySelector('.filtered-search'); @@ -534,6 +551,7 @@ describe('Filtered Search Visual Tokens', () => { expect(input.value).toEqual(''); expect(updatedToken.querySelector('.name').innerText).toEqual('assignee'); + expect(updatedToken.querySelector('.operator').innerText).toEqual('='); expect(updatedToken.querySelector('.value').innerText).toEqual('@john'); }); }); @@ -544,9 +562,9 @@ describe('Filtered Search Visual Tokens', () => { beforeEach(() => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', 'none')} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '=', 'none')} ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search')} - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'upcoming')} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', '=', 'upcoming')} `); input = document.querySelector('.filtered-search'); @@ -614,7 +632,7 @@ describe('Filtered Search Visual Tokens', () => { describe('moveInputTotheRight', () => { it('does nothing if the input is already the right most element', () => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', 'none'), + FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '=', 'none'), ); spyOn(subject, 'tokenizeInput').and.callFake(() => {}); @@ -628,12 +646,12 @@ describe('Filtered Search Visual Tokens', () => { it("tokenize's input", () => { tokensContainer.innerHTML = ` - ${FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('label')} + ${FilteredSearchSpecHelper.createNameOperatorFilterVisualTokenHTML('label', '=')} ${FilteredSearchSpecHelper.createInputHTML()} ${bugLabelToken.outerHTML} `; - document.querySelector('.filtered-search').value = 'none'; + tokensContainer.querySelector('.filtered-search').value = 'none'; subject.moveInputToTheRight(); const value = tokensContainer.querySelector('.js-visual-token .value'); @@ -643,7 +661,7 @@ describe('Filtered Search Visual Tokens', () => { it('converts input into search term token if last token is valid', () => { tokensContainer.innerHTML = ` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', 'none')} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '=', 'none')} ${FilteredSearchSpecHelper.createInputHTML()} ${bugLabelToken.outerHTML} `; @@ -658,7 +676,7 @@ describe('Filtered Search Visual Tokens', () => { it('moves the input to the right most element', () => { tokensContainer.innerHTML = ` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', 'none')} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '=', 'none')} ${FilteredSearchSpecHelper.createInputHTML()} ${bugLabelToken.outerHTML} `; @@ -670,8 +688,8 @@ describe('Filtered Search Visual Tokens', () => { it('tokenizes input even if input is the right most element', () => { tokensContainer.innerHTML = ` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', 'none')} - ${FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('label')} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '=', 'none')} + ${FilteredSearchSpecHelper.createNameOperatorFilterVisualTokenHTML('label')} ${FilteredSearchSpecHelper.createInputHTML('', '~bug')} `; diff --git a/spec/javascripts/filtered_search/issues_filtered_search_token_keys_spec.js b/spec/javascripts/filtered_search/issues_filtered_search_token_keys_spec.js index e33a6c002e5..c7be900ba2c 100644 --- a/spec/javascripts/filtered_search/issues_filtered_search_token_keys_spec.js +++ b/spec/javascripts/filtered_search/issues_filtered_search_token_keys_spec.js @@ -138,6 +138,7 @@ describe('Issues Filtered Search Token Keys', () => { const conditions = IssuableFilteredSearchTokenKeys.getConditions(); const result = IssuableFilteredSearchTokenKeys.searchByConditionKeyValue( conditions[0].tokenKey, + conditions[0].operator, conditions[0].value, ); diff --git a/spec/javascripts/filtered_search/visual_token_value_spec.js b/spec/javascripts/filtered_search/visual_token_value_spec.js index 5863005de1e..a039e280028 100644 --- a/spec/javascripts/filtered_search/visual_token_value_spec.js +++ b/spec/javascripts/filtered_search/visual_token_value_spec.js @@ -10,9 +10,11 @@ describe('Filtered Search Visual Tokens', () => { const tokenNameElement = tokenElement.querySelector('.name'); const tokenValueContainer = tokenElement.querySelector('.value-container'); const tokenValueElement = tokenValueContainer.querySelector('.value'); + const tokenOperatorElement = tokenElement.querySelector('.operator'); const tokenType = tokenNameElement.innerText.toLowerCase(); const tokenValue = tokenValueElement.innerText; - const subject = new VisualTokenValue(tokenValue, tokenType); + const tokenOperator = tokenOperatorElement.innerText; + const subject = new VisualTokenValue(tokenValue, tokenType, tokenOperator); return { subject, tokenValueContainer, tokenValueElement }; }; @@ -28,8 +30,8 @@ describe('Filtered Search Visual Tokens', () => { `); tokensContainer = document.querySelector('.tokens-container'); - authorToken = FilteredSearchSpecHelper.createFilterVisualToken('author', '@user'); - bugLabelToken = FilteredSearchSpecHelper.createFilterVisualToken('label', '~bug'); + authorToken = FilteredSearchSpecHelper.createFilterVisualToken('author', '=', '@user'); + bugLabelToken = FilteredSearchSpecHelper.createFilterVisualToken('label', '=', '~bug'); }); describe('updateUserTokenAppearance', () => { @@ -140,10 +142,12 @@ describe('Filtered Search Visual Tokens', () => { const missingLabelToken = FilteredSearchSpecHelper.createFilterVisualToken( 'label', + '=', '~doesnotexist', ); const spaceLabelToken = FilteredSearchSpecHelper.createFilterVisualToken( 'label', + '=', '~"some space"', ); diff --git a/spec/javascripts/helpers/filtered_search_spec_helper.js b/spec/javascripts/helpers/filtered_search_spec_helper.js index fd06bb1f324..ceb7982bbc3 100644 --- a/spec/javascripts/helpers/filtered_search_spec_helper.js +++ b/spec/javascripts/helpers/filtered_search_spec_helper.js @@ -1,15 +1,17 @@ export default class FilteredSearchSpecHelper { - static createFilterVisualTokenHTML(name, value, isSelected) { - return FilteredSearchSpecHelper.createFilterVisualToken(name, value, isSelected).outerHTML; + static createFilterVisualTokenHTML(name, operator, value, isSelected) { + return FilteredSearchSpecHelper.createFilterVisualToken(name, operator, value, isSelected) + .outerHTML; } - static createFilterVisualToken(name, value, isSelected = false) { + static createFilterVisualToken(name, operator, value, isSelected = false) { const li = document.createElement('li'); li.classList.add('js-visual-token', 'filtered-search-token', `search-token-${name}`); li.innerHTML = ` <div class="selectable ${isSelected ? 'selected' : ''}" role="button"> <div class="name">${name}</div> + <div class="operator">${operator}</div> <div class="value-container"> <div class="value">${value}</div> <div class="remove-token" role="button"> @@ -30,6 +32,15 @@ export default class FilteredSearchSpecHelper { `; } + static createNameOperatorFilterVisualTokenHTML(name, operator) { + return ` + <li class="js-visual-token filtered-search-token"> + <div class="name">${name}</div> + <div class="operator">${operator}</div> + </li> + `; + } + static createSearchVisualToken(name) { const li = document.createElement('li'); li.classList.add('js-visual-token', 'filtered-search-term'); diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 0dd2ebdb70d..8ddb4c23b81 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -8,6 +8,7 @@ issues: - milestone - notes - resource_label_events +- resource_weight_events - sentry_issue - label_links - labels diff --git a/spec/models/resource_weight_event_spec.rb b/spec/models/resource_weight_event_spec.rb new file mode 100644 index 00000000000..2f00204512e --- /dev/null +++ b/spec/models/resource_weight_event_spec.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ResourceWeightEvent, type: :model do + let_it_be(:user1) { create(:user) } + let_it_be(:user2) { create(:user) } + + let_it_be(:issue1) { create(:issue, author: user1) } + let_it_be(:issue2) { create(:issue, author: user1) } + let_it_be(:issue3) { create(:issue, author: user2) } + + describe 'validations' do + it { is_expected.not_to allow_value(nil).for(:user) } + it { is_expected.not_to allow_value(nil).for(:issue) } + it { is_expected.to allow_value(nil).for(:weight) } + end + + describe 'associations' do + it { is_expected.to belong_to(:user) } + it { is_expected.to belong_to(:issue) } + end + + describe '.by_issue' do + let_it_be(:event1) { create(:resource_weight_event, issue: issue1) } + let_it_be(:event2) { create(:resource_weight_event, issue: issue2) } + let_it_be(:event3) { create(:resource_weight_event, issue: issue1) } + + it 'returns the expected records for an issue with events' do + events = ResourceWeightEvent.by_issue(issue1) + + expect(events).to contain_exactly(event1, event3) + end + + it 'returns the expected records for an issue with no events' do + events = ResourceWeightEvent.by_issue(issue3) + + expect(events).to be_empty + end + end + + describe '.created_after' do + let!(:created_at1) { 1.day.ago } + let!(:created_at2) { 2.days.ago } + let!(:created_at3) { 3.days.ago } + + let!(:event1) { create(:resource_weight_event, issue: issue1, created_at: created_at1) } + let!(:event2) { create(:resource_weight_event, issue: issue2, created_at: created_at2) } + let!(:event3) { create(:resource_weight_event, issue: issue2, created_at: created_at3) } + + it 'returns the expected events' do + events = ResourceWeightEvent.created_after(created_at3) + + expect(events).to contain_exactly(event1, event2) + end + + it 'returns no events if time is after last record time' do + events = ResourceWeightEvent.created_after(1.minute.ago) + + expect(events).to be_empty + end + end + + describe '#discussion_id' do + let_it_be(:event) { create(:resource_weight_event, issue: issue1, created_at: Time.utc(2019, 12, 30)) } + + it 'returns the expected id' do + allow(Digest::SHA1).to receive(:hexdigest) + .with("ResourceWeightEvent-2019-12-30 00:00:00 UTC-#{user1.id}") + .and_return('73d167c478') + + expect(event.discussion_id).to eq('73d167c478') + end + end +end diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb index 017e94d04f1..0635c318942 100644 --- a/spec/presenters/ci/build_runner_presenter_spec.rb +++ b/spec/presenters/ci/build_runner_presenter_spec.rb @@ -183,29 +183,81 @@ describe Ci::BuildRunnerPresenter do let(:pipeline) { merge_request.all_pipelines.first } let(:build) { create(:ci_build, ref: pipeline.ref, pipeline: pipeline) } - it 'returns the correct refspecs' do - is_expected - .to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head') - end - - context 'when GIT_DEPTH is zero' do + context 'when depend_on_persistent_pipeline_ref feature flag is enabled' do before do - create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 0, pipeline: build.pipeline) + stub_feature_flags(ci_force_exposing_merge_request_refs: false) + pipeline.persistent_ref.create end it 'returns the correct refspecs' do is_expected - .to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head', - '+refs/heads/*:refs/remotes/origin/*', - '+refs/tags/*:refs/tags/*') + .to contain_exactly("+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}") + end + + context 'when ci_force_exposing_merge_request_refs feature flag is enabled' do + before do + stub_feature_flags(ci_force_exposing_merge_request_refs: true) + end + + it 'returns the correct refspecs' do + is_expected + .to contain_exactly("+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}", + '+refs/merge-requests/1/head:refs/merge-requests/1/head') + end + end + + context 'when GIT_DEPTH is zero' do + before do + create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 0, pipeline: build.pipeline) + end + + it 'returns the correct refspecs' do + is_expected + .to contain_exactly("+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}", + '+refs/heads/*:refs/remotes/origin/*', + '+refs/tags/*:refs/tags/*') + end + end + + context 'when pipeline is legacy detached merge request pipeline' do + let(:merge_request) { create(:merge_request, :with_legacy_detached_merge_request_pipeline) } + + it 'returns the correct refspecs' do + is_expected.to contain_exactly("+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}", + "+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}") + end end end - context 'when pipeline is legacy detached merge request pipeline' do - let(:merge_request) { create(:merge_request, :with_legacy_detached_merge_request_pipeline) } + context 'when depend_on_persistent_pipeline_ref feature flag is disabled' do + before do + stub_feature_flags(depend_on_persistent_pipeline_ref: false) + end it 'returns the correct refspecs' do - is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}") + is_expected + .to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head') + end + + context 'when GIT_DEPTH is zero' do + before do + create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 0, pipeline: build.pipeline) + end + + it 'returns the correct refspecs' do + is_expected + .to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head', + '+refs/heads/*:refs/remotes/origin/*', + '+refs/tags/*:refs/tags/*') + end + end + + context 'when pipeline is legacy detached merge request pipeline' do + let(:merge_request) { create(:merge_request, :with_legacy_detached_merge_request_pipeline) } + + it 'returns the correct refspecs' do + is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}") + end end end end diff --git a/spec/rubocop/cop/migration/add_column_with_default_spec.rb b/spec/rubocop/cop/migration/add_column_with_default_spec.rb index d309a960e2f..f3518f2f058 100644 --- a/spec/rubocop/cop/migration/add_column_with_default_spec.rb +++ b/spec/rubocop/cop/migration/add_column_with_default_spec.rb @@ -16,7 +16,7 @@ describe RuboCop::Cop::Migration::AddColumnWithDefault do it 'does not register any offenses' do expect_no_offenses(<<~RUBY) def up - add_reference(:projects, :users) + add_column_with_default(:ci_build_needs, :artifacts, :boolean, default: true, allow_null: false) end RUBY end diff --git a/spec/services/resource_events/synthetic_label_notes_builder_service_spec.rb b/spec/services/resource_events/synthetic_label_notes_builder_service_spec.rb new file mode 100644 index 00000000000..41902bc1da1 --- /dev/null +++ b/spec/services/resource_events/synthetic_label_notes_builder_service_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ResourceEvents::SyntheticLabelNotesBuilderService do + describe '#execute' do + let!(:user) { create(:user) } + + let!(:issue) { create(:issue, author: user) } + + let!(:event1) { create(:resource_label_event, issue: issue) } + let!(:event2) { create(:resource_label_event, issue: issue) } + let!(:event3) { create(:resource_label_event, issue: issue) } + + it 'returns the expected synthetic notes' do + notes = ResourceEvents::SyntheticLabelNotesBuilderService.new(issue, user).execute + + expect(notes.size).to eq(3) + end + end +end diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb index 350c8a29e87..c8b7a9251a9 100644 --- a/spec/support/helpers/filtered_search_helpers.rb +++ b/spec/support/helpers/filtered_search_helpers.rb @@ -26,7 +26,7 @@ module FilteredSearchHelpers # Select a label clicking in the search dropdown instead # of entering label names on the input. def select_label_on_dropdown(label_title) - input_filtered_search("label:", submit: false) + input_filtered_search("label=", submit: false) within('#js-dropdown-label') do wait_for_requests @@ -71,7 +71,7 @@ module FilteredSearchHelpers end def init_label_search - filtered_search.set('label:') + filtered_search.set('label=') # This ensures the dropdown is shown expect(find('#js-dropdown-label')).not_to have_css('.filter-dropdown-loading') end @@ -90,6 +90,7 @@ module FilteredSearchHelpers el = token_elements[index] expect(el.find('.name')).to have_content(token[:name]) + expect(el.find('.operator')).to have_content(token[:operator]) if token[:operator].present? expect(el.find('.value')).to have_content(token[:value]) if token[:value].present? # gl-emoji content is blank when the emoji unicode is not supported @@ -101,8 +102,8 @@ module FilteredSearchHelpers end end - def create_token(token_name, token_value = nil, symbol = nil) - { name: token_name, value: "#{symbol}#{token_value}" } + def create_token(token_name, token_value = nil, symbol = nil, token_operator = '=') + { name: token_name, operator: token_operator, value: "#{symbol}#{token_value}" } end def author_token(author_name = nil) @@ -113,9 +114,9 @@ module FilteredSearchHelpers create_token('Assignee', assignee_name) end - def milestone_token(milestone_name = nil, has_symbol = true) + def milestone_token(milestone_name = nil, has_symbol = true, operator = '=') symbol = has_symbol ? '%' : nil - create_token('Milestone', milestone_name, symbol) + create_token('Milestone', milestone_name, symbol, operator) end def release_token(release_tag = nil) diff --git a/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb b/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb index 63ed37cde03..3da80541072 100644 --- a/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb +++ b/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb @@ -13,7 +13,7 @@ shared_examples 'issuable user dropdown behaviors' do it 'only includes members of the project/group' do visit issuables_path - filtered_search.set("#{dropdown}:") + filtered_search.set("#{dropdown}=") expect(find("#js-dropdown-#{dropdown} .filter-dropdown")).to have_content(user_in_dropdown.name) expect(find("#js-dropdown-#{dropdown} .filter-dropdown")).not_to have_content(user_not_in_dropdown.name) |