diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js index b52081df646ba6bc41dfc5b910f5a9d659f87262..a5a6b56a0d3937338d78dfcd61d618cc5fc803df 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js @@ -80,30 +80,48 @@ } // Determines the full search query (visual tokens + input) - static getSearchQuery() { - const tokensContainer = document.querySelector('.tokens-container'); + static getSearchQuery(untilInput = false) { + const tokens = [].slice.call(document.querySelectorAll('.tokens-container li')); const values = []; - [].forEach.call(tokensContainer.querySelectorAll('.js-visual-token'), (token) => { - const name = token.querySelector('.name'); - const value = token.querySelector('.value'); - const symbol = value && value.dataset.symbol ? value.dataset.symbol : ''; - let valueText = ''; + if (untilInput) { + const inputIndex = _.findIndex(tokens, t => t.classList.contains('input-token')); + // Add one to include input-token to the tokens array + tokens.splice(inputIndex + 1); + } - if (value && value.innerText) { - valueText = value.innerText; - } - - if (token.className.indexOf('filtered-search-token') !== -1) { - values.push(`${name.innerText.toLowerCase()}:${symbol}${valueText}`); - } else { - values.push(name.innerText); + tokens.forEach((token) => { + if (token.classList.contains('js-visual-token')) { + const name = token.querySelector('.name'); + const value = token.querySelector('.value'); + const symbol = value && value.dataset.symbol ? value.dataset.symbol : ''; + let valueText = ''; + + if (value && value.innerText) { + valueText = value.innerText; + } + + if (token.className.indexOf('filtered-search-token') !== -1) { + values.push(`${name.innerText.toLowerCase()}:${symbol}${valueText}`); + } else { + values.push(name.innerText); + } + } else if (token.classList.contains('input-token')) { + const { isLastVisualTokenValid } = + gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); + + const input = document.querySelector('.filtered-search'); + const inputValue = input && input.value; + + if (isLastVisualTokenValid) { + values.push(inputValue); + } else { + const previous = values.pop(); + values.push(`${previous}${inputValue}`); + } } }); - const input = document.querySelector('.filtered-search'); - values.push(input && input.value); - return values.join(' '); } diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js index 608c65c78a40f59749a27db0b0e6fc0be35e72b6..e1a9707043973ffefb99be08db8c64d9b68778d5 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js @@ -139,7 +139,7 @@ } setDropdown() { - const query = gl.DropdownUtils.getSearchQuery(); + const query = gl.DropdownUtils.getSearchQuery(true); const { lastToken, searchToken } = this.tokenizer.processTokens(query); if (this.currentDropdown) { diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index 58a984048def3f6436caedd7b84831e1f346bbd0..638fe744668cd3a51157b16c9ba80c82de75e172 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -363,10 +363,13 @@ tokenChange() { const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown]; - const currentDropdownRef = dropdown.reference; - this.setDropdownWrapper(); - currentDropdownRef.dispatchInputEvent(); + if (dropdown) { + const currentDropdownRef = dropdown.reference; + + this.setDropdownWrapper(); + currentDropdownRef.dispatchInputEvent(); + } } } diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb index a78dbaf53ed0ac6f732f8094115f1d914d4b0750..96e87c82d2c1c1e799b70daa040b3f119b58e3b5 100644 --- a/spec/features/issues/filtered_search/visual_tokens_spec.rb +++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb @@ -242,6 +242,23 @@ describe 'Visual tokens', js: true, feature: true do 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 + end + + it 'opens hint dropdown' do + expect(page).to have_css('#js-dropdown-hint', visible: true) + end + + it 'opens author dropdown' do + find('#js-dropdown-hint .filter-dropdown .filter-dropdown-item', text: 'author').click + + 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) @@ -319,4 +336,17 @@ describe 'Visual tokens', js: true, feature: true do expect(token.find('.name').text).to eq('Author') end end + + describe 'search using incomplete visual tokens' do + before do + input_filtered_search('author:@root assignee:none', extra_space: false) + end + + it 'tokenizes the search term to complete visual token' do + expect_tokens([ + { name: 'author', value: '@root' }, + { name: 'assignee', value: 'none' } + ]) + end + end end diff --git a/spec/support/filtered_search_helpers.rb b/spec/support/filtered_search_helpers.rb index f21b85ec10b0ef9e0c4abfd5df753b1bad833659..6b009b132b6cc4436e6ba7521927dcea34afe1a5 100644 --- a/spec/support/filtered_search_helpers.rb +++ b/spec/support/filtered_search_helpers.rb @@ -4,9 +4,14 @@ module FilteredSearchHelpers end # Enables input to be set (similar to copy and paste) - def input_filtered_search(search_term, submit: true) - # Add an extra space to engage visual tokens - filtered_search.set("#{search_term} ") + def input_filtered_search(search_term, submit: true, extra_space: true) + search = search_term + if extra_space + # Add an extra space to engage visual tokens + search = "#{search_term} " + end + + filtered_search.set(search) if submit filtered_search.send_keys(:enter)