diff options
19 files changed, 400 insertions, 45 deletions
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index a2664c0301e..178e72a1127 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -139,6 +139,8 @@ import GpgBadges from './gpg_badges'; .init(); } + const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search'); + switch (page) { case 'profiles:preferences:show': initExperimentalFlags(); @@ -155,7 +157,7 @@ import GpgBadges from './gpg_badges'; break; case 'projects:merge_requests:index': case 'projects:issues:index': - if (gl.FilteredSearchManager && document.querySelector('.filtered-search')) { + if (filteredSearchEnabled) { const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests'); filteredSearchManager.setup(); } @@ -183,11 +185,17 @@ import GpgBadges from './gpg_badges'; break; case 'dashboard:issues': case 'dashboard:merge_requests': - case 'groups:issues': case 'groups:merge_requests': new ProjectSelect(); initLegacyFilters(); break; + case 'groups:issues': + if (filteredSearchEnabled) { + const filteredSearchManager = new gl.FilteredSearchManager('issues'); + filteredSearchManager.setup(); + } + new ProjectSelect(); + break; case 'dashboard:todos:index': new Todos(); break; diff --git a/app/assets/javascripts/droplab/plugins/ajax.js b/app/assets/javascripts/droplab/plugins/ajax.js index c0da5866139..267b53fa4f2 100644 --- a/app/assets/javascripts/droplab/plugins/ajax.js +++ b/app/assets/javascripts/droplab/plugins/ajax.js @@ -11,6 +11,16 @@ const Ajax = { if (!self.destroyed) self.hook.list[config.method].call(self.hook.list, data); }, + preprocessing: function preprocessing(config, data) { + let results = data; + + if (config.preprocessing && !data.preprocessed) { + results = config.preprocessing(data); + AjaxCache.override(config.endpoint, results); + } + + return results; + }, init: function init(hook) { var self = this; self.destroyed = false; @@ -31,7 +41,8 @@ const Ajax = { dynamicList.outerHTML = loadingTemplate.outerHTML; } - AjaxCache.retrieve(config.endpoint) + return AjaxCache.retrieve(config.endpoint) + .then(self.preprocessing.bind(null, config)) .then((data) => self._loadData(data, config, self)) .catch(config.onError); }, diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js b/app/assets/javascripts/filtered_search/dropdown_non_user.js index 2615d626c4c..0bc4b6f22a9 100644 --- a/app/assets/javascripts/filtered_search/dropdown_non_user.js +++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js @@ -6,7 +6,7 @@ import './filtered_search_dropdown'; class DropdownNonUser extends gl.FilteredSearchDropdown { constructor(options = {}) { - const { input, endpoint, symbol } = options; + const { input, endpoint, symbol, preprocessing } = options; super(options); this.symbol = symbol; this.config = { @@ -14,6 +14,7 @@ class DropdownNonUser extends gl.FilteredSearchDropdown { endpoint, method: 'setData', loadingTemplate: this.loadingTemplate, + preprocessing, onError() { /* eslint-disable no-new */ new Flash('An error occured fetching the dropdown data.'); diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js index ef8fe071012..9c7a4d9f6ad 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js @@ -50,6 +50,66 @@ class DropdownUtils { return updatedItem; } + static mergeDuplicateLabels(dataMap, newLabel) { + const updatedMap = dataMap; + const key = newLabel.title; + + const hasKeyProperty = Object.prototype.hasOwnProperty.call(updatedMap, key); + + if (!hasKeyProperty) { + updatedMap[key] = newLabel; + } else { + const existing = updatedMap[key]; + + if (!existing.multipleColors) { + existing.multipleColors = [existing.color]; + } + + existing.multipleColors.push(newLabel.color); + } + + return updatedMap; + } + + static duplicateLabelColor(labelColors) { + const colors = labelColors; + const spacing = 100 / colors.length; + + // Reduce the colors to 4 + colors.length = Math.min(colors.length, 4); + + const color = colors.map((c, i) => { + const percentFirst = Math.floor(spacing * i); + const percentSecond = Math.floor(spacing * (i + 1)); + return `${c} ${percentFirst}%, ${c} ${percentSecond}%`; + }).join(', '); + + return `linear-gradient(${color})`; + } + + static duplicateLabelPreprocessing(data) { + const results = []; + const dataMap = {}; + + data.forEach(DropdownUtils.mergeDuplicateLabels.bind(null, dataMap)); + + Object.keys(dataMap) + .forEach((key) => { + const label = dataMap[key]; + + if (label.multipleColors) { + label.color = DropdownUtils.duplicateLabelColor(label.multipleColors); + label.text_color = '#000000'; + } + + results.push(label); + }); + + results.preprocessed = true; + + return results; + } + static filterHint(config, item) { const { input, allowedKeys } = config; const updatedItem = item; 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 61cef435209..47cecd5b5f7 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js @@ -54,6 +54,7 @@ class FilteredSearchDropdownManager { extraArguments: { endpoint: `${this.baseEndpoint}/labels.json`, symbol: '~', + preprocessing: gl.DropdownUtils.duplicateLabelPreprocessing, }, element: this.container.querySelector('#js-dropdown-label'), }, diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index 7872e9e68ad..3ce8b8607ad 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -20,13 +20,13 @@ class FilteredSearchManager { allowedKeys: this.filteredSearchTokenKeys.getKeys(), }); this.searchHistoryDropdownElement = document.querySelector('.js-filtered-search-history-dropdown'); - const projectPath = this.searchHistoryDropdownElement ? - this.searchHistoryDropdownElement.dataset.projectFullPath : 'project'; + const fullPath = this.searchHistoryDropdownElement ? + this.searchHistoryDropdownElement.dataset.fullPath : 'project'; let recentSearchesPagePrefix = 'issue-recent-searches'; if (this.page === 'merge_requests') { recentSearchesPagePrefix = 'merge-request-recent-searches'; } - const recentSearchesKey = `${projectPath}-${recentSearchesPagePrefix}`; + const recentSearchesKey = `${fullPath}-${recentSearchesPagePrefix}`; this.recentSearchesService = new RecentSearchesService(recentSearchesKey); } diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js index e9278140af0..243ee4d723a 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js +++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js @@ -58,29 +58,54 @@ class FilteredSearchVisualTokens { `; } + static setTokenStyle(tokenContainer, backgroundColor, textColor) { + const token = tokenContainer; + + // Labels with linear gradient should not override default background color + if (backgroundColor.indexOf('linear-gradient') === -1) { + token.style.backgroundColor = backgroundColor; + } + + token.style.color = textColor; + + if (textColor === '#FFFFFF') { + const removeToken = token.querySelector('.remove-token'); + removeToken.classList.add('inverted'); + } + + return token; + } + + static preprocessLabel(labelsEndpoint, labels) { + let processed = labels; + + if (!labels.preprocessed) { + processed = gl.DropdownUtils.duplicateLabelPreprocessing(labels); + AjaxCache.override(labelsEndpoint, processed); + processed.preprocessed = true; + } + + return processed; + } + static updateLabelTokenColor(tokenValueContainer, tokenValue) { const filteredSearchInput = FilteredSearchContainer.container.querySelector('.filtered-search'); const baseEndpoint = filteredSearchInput.dataset.baseEndpoint; const labelsEndpoint = `${baseEndpoint}/labels.json`; return AjaxCache.retrieve(labelsEndpoint) - .then((labels) => { - const matchingLabel = (labels || []).find(label => `~${gl.DropdownUtils.getEscapedText(label.title)}` === tokenValue); - - if (!matchingLabel) { - return; - } + .then(FilteredSearchVisualTokens.preprocessLabel.bind(null, labelsEndpoint)) + .then((labels) => { + const matchingLabel = (labels || []).find(label => `~${gl.DropdownUtils.getEscapedText(label.title)}` === tokenValue); - const tokenValueStyle = tokenValueContainer.style; - tokenValueStyle.backgroundColor = matchingLabel.color; - tokenValueStyle.color = matchingLabel.text_color; + if (!matchingLabel) { + return; + } - if (matchingLabel.text_color === '#FFFFFF') { - const removeToken = tokenValueContainer.querySelector('.remove-token'); - removeToken.classList.add('inverted'); - } - }) - .catch(() => new Flash('An error occurred while fetching label colors.')); + FilteredSearchVisualTokens + .setTokenStyle(tokenValueContainer, matchingLabel.color, matchingLabel.text_color); + }) + .catch(() => new Flash('An error occurred while fetching label colors.')); } static updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) { diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 8d7d3d73571..f0e02ca0fb2 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -3,6 +3,7 @@ /* global ListLabel */ import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; +import DropdownUtils from './filtered_search/dropdown_utils'; (function() { this.LabelsSelect = (function() { @@ -218,18 +219,7 @@ import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; } } if (label.duplicate) { - spacing = 100 / label.color.length; - // Reduce the colors to 4 - label.color = label.color.filter(function(color, i) { - return i < 4; - }); - color = _.map(label.color, function(color, i) { - var percentFirst, percentSecond; - percentFirst = Math.floor(spacing * i); - percentSecond = Math.floor(spacing * (i + 1)); - return color + " " + percentFirst + "%," + color + " " + percentSecond + "% "; - }).join(','); - color = "linear-gradient(" + color + ")"; + color = gl.DropdownUtils.duplicateLabelColor(label.color); } else { if (label.color != null) { diff --git a/app/assets/javascripts/lib/utils/ajax_cache.js b/app/assets/javascripts/lib/utils/ajax_cache.js index 7477b5a5214..629d8f44e18 100644 --- a/app/assets/javascripts/lib/utils/ajax_cache.js +++ b/app/assets/javascripts/lib/utils/ajax_cache.js @@ -6,6 +6,10 @@ class AjaxCache extends Cache { this.pendingRequests = { }; } + override(endpoint, data) { + this.internalStorage[endpoint] = data; + } + retrieve(endpoint, forceRetrieve) { if (this.hasData(endpoint) && !forceRetrieve) { return Promise.resolve(this.get(endpoint)); diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index fd7ab59ce64..ae0e0aa3cf9 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -127,15 +127,23 @@ module SearchHelper end def search_filter_input_options(type) - { + opts = { id: "filtered-search-#{type}", placeholder: 'Search or filter results...', data: { - 'project-id' => @project.id, - 'username-params' => @users.to_json(only: [:id, :username]), - 'base-endpoint' => project_path(@project) + 'username-params' => @users.to_json(only: [:id, :username]) } } + + if @project.present? + opts[:data]['project-id'] = @project.id + opts[:data]['base-endpoint'] = project_path(@project) + else + # Group context + opts[:data]['base-endpoint'] = group_canonical_path(@group) + end + + opts end # Sanitize a HTML field for search display. Most tags are stripped out and the diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 735d9390699..f83ebbf09ef 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -4,6 +4,10 @@ = content_for :meta_tags do = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@group.name} issues") +- content_for :page_specific_javascripts do + = webpack_bundle_tag 'common_vue' + = webpack_bundle_tag 'filtered_search' + - if show_new_nav? && group_issues_exists - content_for :breadcrumbs_extra do = link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10' do @@ -20,7 +24,7 @@ Subscribe = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue" - = render 'shared/issuable/filter', type: :issues + = render 'shared/issuable/search_bar', type: :issues .row-content-block.second-block Only issues from the diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 3428d6e0445..1ad00461d76 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -1,5 +1,6 @@ - type = local_assigns.fetch(:type) - block_css_class = type != :boards_modal ? 'row-content-block second-block' : '' +- full_path = @project.present? ? @project.full_path : @group.full_path .issues-filters .issues-details-filters.filtered-search-block{ class: block_css_class, "v-pre" => type == :boards_modal } @@ -18,7 +19,7 @@ dropdown_class: "filtered-search-history-dropdown", content_class: "filtered-search-history-dropdown-content", title: "Recent searches" }) do - .js-filtered-search-history-dropdown{ data: { project_full_path: @project.full_path } } + .js-filtered-search-history-dropdown{ data: { full_path: full_path } } .filtered-search-box-input-container.droplab-dropdown .scroll-container %ul.tokens-container.list-unstyled diff --git a/changelogs/unreleased/add-filtered-search-group-issues-ce.yml b/changelogs/unreleased/add-filtered-search-group-issues-ce.yml new file mode 100644 index 00000000000..f83f4173890 --- /dev/null +++ b/changelogs/unreleased/add-filtered-search-group-issues-ce.yml @@ -0,0 +1,4 @@ +--- +title: Add filtered search to group issue dashboard +merge_request: +author: diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb index 449a99a2c7b..cdf7aceb13c 100644 --- a/spec/features/groups/issues_spec.rb +++ b/spec/features/groups/issues_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' feature 'Group issues page' do + include FilteredSearchHelpers + let(:path) { issues_group_path(group) } let(:issuable) { create(:issue, project: project, title: "this is my created issuable")} @@ -31,12 +33,10 @@ feature 'Group issues page' do let(:path) { issues_group_path(group) } it 'filters by only group users' do - click_button('Assignee') - - wait_for_requests + filtered_search.set('assignee:') - expect(find('.dropdown-menu-assignee')).to have_link(user.name) - expect(find('.dropdown-menu-assignee')).not_to have_link(user2.name) + 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) end end end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index b7e547dc1f5..4dab21440a6 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -68,4 +68,38 @@ describe SearchHelper do end end end + + describe 'search_filter_input_options' do + context 'project' do + before do + @project = create(:project, :repository) + end + + it 'includes id with type' do + expect(search_filter_input_options('type')[:id]).to eq('filtered-search-type') + end + + it 'includes project-id' do + expect(search_filter_input_options('')[:data]['project-id']).to eq(@project.id) + end + + it 'includes project base-endpoint' do + expect(search_filter_input_options('')[:data]['base-endpoint']).to eq(project_path(@project)) + end + end + + context 'group' do + before do + @group = create(:group, name: 'group') + end + + it 'does not includes project-id' do + expect(search_filter_input_options('')[:data]['project-id']).to eq(nil) + end + + it 'includes group base-endpoint' do + expect(search_filter_input_options('')[:data]['base-endpoint']).to eq("/groups#{group_path(@group)}") + end + end + end end diff --git a/spec/javascripts/droplab/plugins/ajax_spec.js b/spec/javascripts/droplab/plugins/ajax_spec.js new file mode 100644 index 00000000000..085f25764fe --- /dev/null +++ b/spec/javascripts/droplab/plugins/ajax_spec.js @@ -0,0 +1,36 @@ +import AjaxCache from '~/lib/utils/ajax_cache'; +import Ajax from '~/droplab/plugins/ajax'; + +describe('Ajax', () => { + describe('preprocessing', () => { + const config = {}; + + describe('is not configured', () => { + it('passes the data through', () => { + const data = ['data']; + expect(Ajax.preprocessing(config, data)).toEqual(data); + }); + }); + + describe('is configured', () => { + const processedArray = ['processed']; + + beforeEach(() => { + config.preprocessing = () => processedArray; + spyOn(config, 'preprocessing').and.callFake(() => processedArray); + }); + + it('calls preprocessing', () => { + Ajax.preprocessing(config, []); + expect(config.preprocessing.calls.count()).toBe(1); + }); + + it('overrides AjaxCache', () => { + spyOn(AjaxCache, 'override').and.callFake((endpoint, results) => expect(results).toEqual(processedArray)); + + Ajax.preprocessing(config, []); + expect(AjaxCache.override.calls.count()).toBe(1); + }); + }); + }); +}); diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js index f55726379f3..244f170ab7a 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js @@ -191,6 +191,102 @@ describe('Dropdown Utils', () => { }); }); + describe('mergeDuplicateLabels', () => { + const dataMap = { + label: { + title: 'label', + color: '#FFFFFF', + }, + }; + + it('should add label to dataMap if it is not a duplicate', () => { + const newLabel = { + title: 'new-label', + color: '#000000', + }; + + const updated = gl.DropdownUtils.mergeDuplicateLabels(dataMap, newLabel); + expect(updated[newLabel.title]).toEqual(newLabel); + }); + + it('should merge colors if label is a duplicate', () => { + const duplicate = { + title: 'label', + color: '#000000', + }; + + const updated = gl.DropdownUtils.mergeDuplicateLabels(dataMap, duplicate); + expect(updated.label.multipleColors).toEqual([dataMap.label.color, duplicate.color]); + }); + }); + + describe('duplicateLabelColor', () => { + it('should linear-gradient 2 colors', () => { + const gradient = gl.DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000']); + expect(gradient).toEqual('linear-gradient(#FFFFFF 0%, #FFFFFF 50%, #000000 50%, #000000 100%)'); + }); + + it('should linear-gradient 3 colors', () => { + const gradient = gl.DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000', '#333333']); + expect(gradient).toEqual('linear-gradient(#FFFFFF 0%, #FFFFFF 33%, #000000 33%, #000000 66%, #333333 66%, #333333 100%)'); + }); + + it('should linear-gradient 4 colors', () => { + const gradient = gl.DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000', '#333333', '#DDDDDD']); + expect(gradient).toEqual('linear-gradient(#FFFFFF 0%, #FFFFFF 25%, #000000 25%, #000000 50%, #333333 50%, #333333 75%, #DDDDDD 75%, #DDDDDD 100%)'); + }); + + it('should not linear-gradient more than 4 colors', () => { + const gradient = gl.DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000', '#333333', '#DDDDDD', '#EEEEEE']); + expect(gradient.indexOf('#EEEEEE') === -1).toEqual(true); + }); + }); + + describe('duplicateLabelPreprocessing', () => { + it('should set preprocessed to true', () => { + const results = gl.DropdownUtils.duplicateLabelPreprocessing([]); + expect(results.preprocessed).toEqual(true); + }); + + it('should not mutate existing data if there are no duplicates', () => { + const data = [{ + title: 'label1', + color: '#FFFFFF', + }, { + title: 'label2', + color: '#000000', + }]; + const results = gl.DropdownUtils.duplicateLabelPreprocessing(data); + + expect(results.length).toEqual(2); + expect(results[0]).toEqual(data[0]); + expect(results[1]).toEqual(data[1]); + }); + + describe('duplicate labels', () => { + const data = [{ + title: 'label', + color: '#FFFFFF', + }, { + title: 'label', + color: '#000000', + }]; + const results = gl.DropdownUtils.duplicateLabelPreprocessing(data); + + it('should merge duplicate labels', () => { + expect(results.length).toEqual(1); + }); + + it('should convert multiple colored labels into linear-gradient', () => { + expect(results[0].color).toEqual(gl.DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000'])); + }); + + it('should set multiple colored label text color to black', () => { + expect(results[0].text_color).toEqual('#000000'); + }); + }); + }); + describe('setDataValueIfSelected', () => { beforeEach(() => { spyOn(gl.FilteredSearchDropdownManager, 'addWordToInput') 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 fa4343ffbc8..67166802c70 100644 --- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js @@ -797,6 +797,69 @@ describe('Filtered Search Visual Tokens', () => { }); }); + describe('setTokenStyle', () => { + let originalTextColor; + + beforeEach(() => { + originalTextColor = bugLabelToken.style.color; + }); + + it('should set backgroundColor', () => { + const originalBackgroundColor = bugLabelToken.style.backgroundColor; + const token = subject.setTokenStyle(bugLabelToken, 'blue', 'white'); + expect(token.style.backgroundColor).toEqual('blue'); + expect(token.style.backgroundColor).not.toEqual(originalBackgroundColor); + }); + + it('should not set backgroundColor when it is a linear-gradient', () => { + const token = subject.setTokenStyle(bugLabelToken, 'linear-gradient(135deg, red, blue)', 'white'); + expect(token.style.backgroundColor).toEqual(bugLabelToken.style.backgroundColor); + }); + + it('should set textColor', () => { + const token = subject.setTokenStyle(bugLabelToken, 'white', 'black'); + expect(token.style.color).toEqual('black'); + expect(token.style.color).not.toEqual(originalTextColor); + }); + + it('should add inverted class when textColor is #FFFFFF', () => { + const token = subject.setTokenStyle(bugLabelToken, 'black', '#FFFFFF'); + expect(token.style.color).toEqual('rgb(255, 255, 255)'); + expect(token.style.color).not.toEqual(originalTextColor); + expect(token.querySelector('.remove-token').classList.contains('inverted')).toEqual(true); + }); + }); + + describe('preprocessLabel', () => { + const endpoint = 'endpoint'; + + it('does not preprocess more than once', () => { + let labels = []; + + spyOn(gl.DropdownUtils, 'duplicateLabelPreprocessing').and.callFake(() => []); + + labels = gl.FilteredSearchVisualTokens.preprocessLabel(endpoint, labels); + gl.FilteredSearchVisualTokens.preprocessLabel(endpoint, labels); + + expect(gl.DropdownUtils.duplicateLabelPreprocessing.calls.count()).toEqual(1); + }); + + describe('not preprocessed before', () => { + it('returns preprocessed labels', () => { + let labels = []; + expect(labels.preprocessed).not.toEqual(true); + labels = gl.FilteredSearchVisualTokens.preprocessLabel(endpoint, labels); + expect(labels.preprocessed).toEqual(true); + }); + + it('overrides AjaxCache with preprocessed results', () => { + spyOn(AjaxCache, 'override').and.callFake(() => {}); + gl.FilteredSearchVisualTokens.preprocessLabel(endpoint, []); + expect(AjaxCache.override.calls.count()).toEqual(1); + }); + }); + }); + describe('updateLabelTokenColor', () => { const jsonFixtureName = 'labels/project_labels.json'; const dummyEndpoint = '/dummy/endpoint'; diff --git a/spec/javascripts/lib/utils/ajax_cache_spec.js b/spec/javascripts/lib/utils/ajax_cache_spec.js index 2c946802dcd..49971bd91e2 100644 --- a/spec/javascripts/lib/utils/ajax_cache_spec.js +++ b/spec/javascripts/lib/utils/ajax_cache_spec.js @@ -77,6 +77,15 @@ describe('AjaxCache', () => { }); }); + describe('override', () => { + it('overrides existing cache', () => { + AjaxCache.internalStorage.endpoint = 'existing-endpoint'; + AjaxCache.override('endpoint', 'new-endpoint'); + + expect(AjaxCache.internalStorage.endpoint).toEqual('new-endpoint'); + }); + }); + describe('retrieve', () => { let ajaxSpy; |