@gitlab/ui 36.0.0 → 36.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/components/base/filtered_search/filtered_search.documentation.js +6 -0
  3. package/dist/components/base/filtered_search/filtered_search.js +12 -2
  4. package/dist/components/base/filtered_search/filtered_search_term.documentation.js +6 -0
  5. package/dist/components/base/filtered_search/filtered_search_term.js +11 -1
  6. package/dist/components/base/filtered_search/filtered_search_token_segment.documentation.js +6 -0
  7. package/dist/components/base/filtered_search/filtered_search_token_segment.js +11 -1
  8. package/dist/components/base/search_box_by_click/search_box_by_click.documentation.js +3 -0
  9. package/dist/components/base/search_box_by_click/search_box_by_click.js +6 -1
  10. package/package.json +1 -1
  11. package/src/components/base/filtered_search/filtered_search.documentation.js +6 -0
  12. package/src/components/base/filtered_search/filtered_search.spec.js +81 -47
  13. package/src/components/base/filtered_search/filtered_search.vue +13 -0
  14. package/src/components/base/filtered_search/filtered_search_term.documentation.js +6 -0
  15. package/src/components/base/filtered_search/filtered_search_term.spec.js +42 -9
  16. package/src/components/base/filtered_search/filtered_search_term.vue +13 -0
  17. package/src/components/base/filtered_search/filtered_search_token_segment.documentation.js +6 -0
  18. package/src/components/base/filtered_search/filtered_search_token_segment.spec.js +53 -0
  19. package/src/components/base/filtered_search/filtered_search_token_segment.vue +12 -0
  20. package/src/components/base/search_box_by_click/search_box_by_click.documentation.js +3 -0
  21. package/src/components/base/search_box_by_click/search_box_by_click.spec.js +14 -3
  22. package/src/components/base/search_box_by_click/search_box_by_click.vue +7 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [36.1.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v36.0.0...v36.1.0) (2022-02-08)
2
+
3
+
4
+ ### Features
5
+
6
+ * **GlFilteredSearch:** add props for adding additional HTML attributes ([b26ea14](https://gitlab.com/gitlab-org/gitlab-ui/commit/b26ea14b818cef389c48617cb3443ae493cc49aa))
7
+
1
8
  # [36.0.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v35.1.0...v36.0.0) (2022-02-07)
2
9
 
3
10
 
@@ -39,6 +39,12 @@ var filtered_search_documentation = {
39
39
  },
40
40
  suggestionsListClass: {
41
41
  additionalInfo: 'Additional classes to add to the suggestion list menu. NOTE: this not reactive, and the value must be available and fixed when the component is instantiated'
42
+ },
43
+ searchButtonAttributes: {
44
+ additionalInfo: 'HTML attributes to add to the search button'
45
+ },
46
+ searchInputAttributes: {
47
+ additionalInfo: 'HTML attributes to add to the search input'
42
48
  }
43
49
  },
44
50
  events: [{
@@ -83,6 +83,16 @@ var script = {
83
83
  type: Boolean,
84
84
  required: false,
85
85
  default: false
86
+ },
87
+ searchButtonAttributes: {
88
+ type: Object,
89
+ required: false,
90
+ default: () => ({})
91
+ },
92
+ searchInputAttributes: {
93
+ type: Object,
94
+ required: false,
95
+ default: () => ({})
86
96
  }
87
97
  },
88
98
 
@@ -269,9 +279,9 @@ var script = {
269
279
  const __vue_script__ = script;
270
280
 
271
281
  /* template */
272
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-search-box-by-click',_vm._b({attrs:{"value":_vm.tokens,"history-items":_vm.historyItems,"clearable":_vm.hasValue,"data-testid":"filtered-search-input"},on:{"submit":_vm.submit,"input":_vm.applyNewValue,"history-item-selected":function($event){return _vm.$emit('history-item-selected', $event)},"clear":function($event){return _vm.$emit('clear')},"clear-history":function($event){return _vm.$emit('clear-history')}},scopedSlots:_vm._u([{key:"history-item",fn:function(slotScope){return [_vm._t("history-item",null,null,slotScope)]}},{key:"input",fn:function(){return [_c('div',{staticClass:"gl-filtered-search-scrollable"},[_vm._l((_vm.tokens),function(token,idx){return [_c(_vm.getTokenComponent(token.type),{key:((token.type) + "-" + idx),ref:"tokens",refInFor:true,tag:"component",staticClass:"gl-filtered-search-item",class:{
282
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-search-box-by-click',_vm._b({attrs:{"value":_vm.tokens,"history-items":_vm.historyItems,"clearable":_vm.hasValue,"search-button-attributes":_vm.searchButtonAttributes,"data-testid":"filtered-search-input"},on:{"submit":_vm.submit,"input":_vm.applyNewValue,"history-item-selected":function($event){return _vm.$emit('history-item-selected', $event)},"clear":function($event){return _vm.$emit('clear')},"clear-history":function($event){return _vm.$emit('clear-history')}},scopedSlots:_vm._u([{key:"history-item",fn:function(slotScope){return [_vm._t("history-item",null,null,slotScope)]}},{key:"input",fn:function(){return [_c('div',{staticClass:"gl-filtered-search-scrollable"},[_vm._l((_vm.tokens),function(token,idx){return [_c(_vm.getTokenComponent(token.type),{key:((token.type) + "-" + idx),ref:"tokens",refInFor:true,tag:"component",staticClass:"gl-filtered-search-item",class:{
273
283
  'gl-filtered-search-last-item': _vm.isLastToken(idx),
274
- },attrs:{"config":_vm.getTokenEntry(token.type),"active":_vm.activeTokenIdx === idx,"available-tokens":_vm.currentAvailableTokens,"current-value":_vm.tokens,"index":idx,"placeholder":_vm.termPlaceholder,"show-friendly-text":_vm.showFriendlyText},on:{"activate":function($event){return _vm.activate(idx)},"deactivate":function($event){return _vm.deactivate(token)},"destroy":function($event){return _vm.destroyToken(idx)},"replace":function($event){return _vm.replaceToken(idx, $event)},"complete":_vm.completeToken,"submit":_vm.submit,"split":function($event){return _vm.createTokens(idx, $event)}},model:{value:(token.value),callback:function ($$v) {_vm.$set(token, "value", $$v);},expression:"token.value"}})]})],2),_vm._v(" "),_c('portal-target',{key:_vm.activeTokenIdx,ref:"menu",style:(_vm.suggestionsStyle),attrs:{"name":_vm.portalName,"slim":""}})]},proxy:true}],null,true)},'gl-search-box-by-click',_vm.$attrs,false))};
284
+ },attrs:{"config":_vm.getTokenEntry(token.type),"active":_vm.activeTokenIdx === idx,"available-tokens":_vm.currentAvailableTokens,"current-value":_vm.tokens,"index":idx,"placeholder":_vm.termPlaceholder,"show-friendly-text":_vm.showFriendlyText,"search-input-attributes":_vm.searchInputAttributes,"is-last-token":_vm.isLastToken(idx)},on:{"activate":function($event){return _vm.activate(idx)},"deactivate":function($event){return _vm.deactivate(token)},"destroy":function($event){return _vm.destroyToken(idx)},"replace":function($event){return _vm.replaceToken(idx, $event)},"complete":_vm.completeToken,"submit":_vm.submit,"split":function($event){return _vm.createTokens(idx, $event)}},model:{value:(token.value),callback:function ($$v) {_vm.$set(token, "value", $$v);},expression:"token.value"}})]})],2),_vm._v(" "),_c('portal-target',{key:_vm.activeTokenIdx,ref:"menu",style:(_vm.suggestionsStyle),attrs:{"name":_vm.portalName,"slim":""}})]},proxy:true}],null,true)},'gl-search-box-by-click',_vm.$attrs,false))};
275
285
  var __vue_staticRenderFns__ = [];
276
286
 
277
287
  /* style */
@@ -17,6 +17,12 @@ var filtered_search_term_documentation = {
17
17
  },
18
18
  value: {
19
19
  additionalInfo: 'Current term value'
20
+ },
21
+ searchInputAttributes: {
22
+ additionalInfo: 'HTML attributes to add to the search input'
23
+ },
24
+ isLastToken: {
25
+ additionalInfo: 'If this is the last token'
20
26
  }
21
27
  },
22
28
  events: [{
@@ -29,6 +29,16 @@ var script = {
29
29
  type: String,
30
30
  required: false,
31
31
  default: ''
32
+ },
33
+ searchInputAttributes: {
34
+ type: Object,
35
+ required: false,
36
+ default: () => ({})
37
+ },
38
+ isLastToken: {
39
+ type: Boolean,
40
+ required: false,
41
+ default: false
32
42
  }
33
43
  },
34
44
  computed: {
@@ -55,7 +65,7 @@ var script = {
55
65
  const __vue_script__ = script;
56
66
 
57
67
  /* template */
58
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-h-auto gl-filtered-search-term"},[_c('gl-filtered-search-token-segment',{staticClass:"gl-filtered-search-term-token",class:{ 'gl-w-full': _vm.placeholder },attrs:{"active":_vm.active},on:{"activate":function($event){return _vm.$emit('activate')},"deactivate":function($event){return _vm.$emit('deactivate')},"complete":function($event){return _vm.$emit('replace', { type: $event })},"backspace":function($event){return _vm.$emit('destroy')},"submit":function($event){return _vm.$emit('submit')},"split":function($event){return _vm.$emit('split', $event)}},scopedSlots:_vm._u([{key:"suggestions",fn:function(){return _vm._l((_vm.suggestedTokens),function(item,idx){return _c('gl-filtered-search-suggestion',{key:idx,attrs:{"value":item.type,"icon-name":item.icon}},[_vm._v("\n "+_vm._s(item.title)+"\n ")])})},proxy:true},{key:"view",fn:function(){return [(_vm.placeholder)?_c('input',{staticClass:"gl-filtered-search-term-input",attrs:{"placeholder":_vm.placeholder,"aria-label":_vm.placeholder}}):[_vm._v(_vm._s(_vm.value.data))]]},proxy:true}]),model:{value:(_vm.internalValue),callback:function ($$v) {_vm.internalValue=$$v;},expression:"internalValue"}})],1)};
68
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-h-auto gl-filtered-search-term"},[_c('gl-filtered-search-token-segment',{staticClass:"gl-filtered-search-term-token",class:{ 'gl-w-full': _vm.placeholder },attrs:{"active":_vm.active,"search-input-attributes":_vm.searchInputAttributes,"is-last-token":_vm.isLastToken},on:{"activate":function($event){return _vm.$emit('activate')},"deactivate":function($event){return _vm.$emit('deactivate')},"complete":function($event){return _vm.$emit('replace', { type: $event })},"backspace":function($event){return _vm.$emit('destroy')},"submit":function($event){return _vm.$emit('submit')},"split":function($event){return _vm.$emit('split', $event)}},scopedSlots:_vm._u([{key:"suggestions",fn:function(){return _vm._l((_vm.suggestedTokens),function(item,idx){return _c('gl-filtered-search-suggestion',{key:idx,attrs:{"value":item.type,"icon-name":item.icon}},[_vm._v("\n "+_vm._s(item.title)+"\n ")])})},proxy:true},{key:"view",fn:function(){return [(_vm.placeholder)?_c('input',_vm._b({staticClass:"gl-filtered-search-term-input",attrs:{"placeholder":_vm.placeholder,"aria-label":_vm.placeholder}},'input',_vm.searchInputAttributes,false)):[_vm._v(_vm._s(_vm.value.data))]]},proxy:true}]),model:{value:(_vm.internalValue),callback:function ($$v) {_vm.internalValue=$$v;},expression:"internalValue"}})],1)};
59
69
  var __vue_staticRenderFns__ = [];
60
70
 
61
71
  /* style */
@@ -23,6 +23,12 @@ var filtered_search_token_segment_documentation = {
23
23
  },
24
24
  value: {
25
25
  additionalInfo: 'Current term value'
26
+ },
27
+ searchInputAttributes: {
28
+ additionalInfo: 'HTML attributes to add to the search input'
29
+ },
30
+ isLastToken: {
31
+ additionalInfo: 'If this is the last token'
26
32
  }
27
33
  },
28
34
  events: [{
@@ -48,6 +48,16 @@ var script = {
48
48
  value: {
49
49
  required: true,
50
50
  validator: () => true
51
+ },
52
+ searchInputAttributes: {
53
+ type: Object,
54
+ required: false,
55
+ default: () => ({})
56
+ },
57
+ isLastToken: {
58
+ type: Boolean,
59
+ required: false,
60
+ default: false
51
61
  }
52
62
  },
53
63
 
@@ -274,7 +284,7 @@ var script = {
274
284
  const __vue_script__ = script;
275
285
 
276
286
  /* template */
277
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-filtered-search-token-segment",class:{ 'gl-filtered-search-token-segment-active': _vm.active },attrs:{"data-testid":"filtered-search-token-segment"},on:{"mousedown":function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"left",37,$event.key,["Left","ArrowLeft"])){ return null; }if('button' in $event && $event.button !== 0){ return null; }return _vm.emitIfInactive($event)}}},[(_vm.active)?[_c('input',{directives:[{name:"model",rawName:"v-model",value:(_vm.inputValue),expression:"inputValue"}],ref:"input",staticClass:"gl-filtered-search-token-segment-input",attrs:{"aria-label":_vm.label},domProps:{"value":(_vm.inputValue)},on:{"keydown":_vm.handleInputKeydown,"blur":_vm.handleBlur,"input":function($event){if($event.target.composing){ return; }_vm.inputValue=$event.target.value;}}}),_vm._v(" "),_c('portal',{key:("operator-" + _vm._uid),attrs:{"to":_vm.portalName}},[(_vm.hasOptionsOrSuggestions)?_c('gl-filtered-search-suggestion-list',{key:("operator-" + _vm._uid),ref:"suggestions",attrs:{"initial-value":_vm.defaultSuggestedValue},on:{"suggestion":_vm.applySuggestion}},[(_vm.options)?_vm._l((_vm.options),function(option,idx){return _c('gl-filtered-search-suggestion',{key:((option.value) + "-" + idx),attrs:{"value":option.value,"icon-name":option.icon}},[_vm._t("option",[_vm._v("\n "+_vm._s(option[_vm.optionTextField])+"\n ")],null,{ option: option })],2)}):_vm._t("suggestions")],2):_vm._e()],1)]:_vm._t("view",[_vm._v(_vm._s(_vm.inputValue))],null,{ inputValue: _vm.inputValue })],2)};
287
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',_vm._b({staticClass:"gl-filtered-search-token-segment",class:{ 'gl-filtered-search-token-segment-active': _vm.active },attrs:{"data-testid":"filtered-search-token-segment"},on:{"mousedown":function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"left",37,$event.key,["Left","ArrowLeft"])){ return null; }if('button' in $event && $event.button !== 0){ return null; }return _vm.emitIfInactive($event)}}},'div',_vm.isLastToken && !_vm.active && _vm.searchInputAttributes,false),[(_vm.active)?[(((_vm.searchInputAttributes).type)==='checkbox')?_c('input',_vm._b({directives:[{name:"model",rawName:"v-model",value:(_vm.inputValue),expression:"inputValue"}],ref:"input",staticClass:"gl-filtered-search-token-segment-input",attrs:{"aria-label":_vm.label,"type":"checkbox"},domProps:{"checked":Array.isArray(_vm.inputValue)?_vm._i(_vm.inputValue,null)>-1:(_vm.inputValue)},on:{"keydown":_vm.handleInputKeydown,"blur":_vm.handleBlur,"change":function($event){var $$a=_vm.inputValue,$$el=$event.target,$$c=$$el.checked?(true):(false);if(Array.isArray($$a)){var $$v=null,$$i=_vm._i($$a,$$v);if($$el.checked){$$i<0&&(_vm.inputValue=$$a.concat([$$v]));}else {$$i>-1&&(_vm.inputValue=$$a.slice(0,$$i).concat($$a.slice($$i+1)));}}else {_vm.inputValue=$$c;}}}},'input',_vm.searchInputAttributes,false)):(((_vm.searchInputAttributes).type)==='radio')?_c('input',_vm._b({directives:[{name:"model",rawName:"v-model",value:(_vm.inputValue),expression:"inputValue"}],ref:"input",staticClass:"gl-filtered-search-token-segment-input",attrs:{"aria-label":_vm.label,"type":"radio"},domProps:{"checked":_vm._q(_vm.inputValue,null)},on:{"keydown":_vm.handleInputKeydown,"blur":_vm.handleBlur,"change":function($event){_vm.inputValue=null;}}},'input',_vm.searchInputAttributes,false)):_c('input',_vm._b({directives:[{name:"model",rawName:"v-model",value:(_vm.inputValue),expression:"inputValue"}],ref:"input",staticClass:"gl-filtered-search-token-segment-input",attrs:{"aria-label":_vm.label,"type":(_vm.searchInputAttributes).type},domProps:{"value":(_vm.inputValue)},on:{"keydown":_vm.handleInputKeydown,"blur":_vm.handleBlur,"input":function($event){if($event.target.composing){ return; }_vm.inputValue=$event.target.value;}}},'input',_vm.searchInputAttributes,false)),_vm._v(" "),_c('portal',{key:("operator-" + _vm._uid),attrs:{"to":_vm.portalName}},[(_vm.hasOptionsOrSuggestions)?_c('gl-filtered-search-suggestion-list',{key:("operator-" + _vm._uid),ref:"suggestions",attrs:{"initial-value":_vm.defaultSuggestedValue},on:{"suggestion":_vm.applySuggestion}},[(_vm.options)?_vm._l((_vm.options),function(option,idx){return _c('gl-filtered-search-suggestion',{key:((option.value) + "-" + idx),attrs:{"value":option.value,"icon-name":option.icon}},[_vm._t("option",[_vm._v("\n "+_vm._s(option[_vm.optionTextField])+"\n ")],null,{ option: option })],2)}):_vm._t("suggestions")],2):_vm._e()],1)]:_vm._t("view",[_vm._v(_vm._s(_vm.inputValue))],null,{ inputValue: _vm.inputValue })],2)};
278
288
  var __vue_staticRenderFns__ = [];
279
289
 
280
290
  /* style */
@@ -51,6 +51,9 @@ var search_box_by_click_documentation = {
51
51
  },
52
52
  tooltipContainer: {
53
53
  additionalInfo: 'Container for tooltip. Valid values: DOM node, selector string or `false` for default'
54
+ },
55
+ searchButtonAttributes: {
56
+ additionalInfo: 'HTML attributes to add to the search button'
54
57
  }
55
58
  },
56
59
  events: [{
@@ -82,6 +82,11 @@ var script = {
82
82
  required: false,
83
83
  default: false,
84
84
  validator: value => value === false || typeof value === 'string' || value instanceof HTMLElement
85
+ },
86
+ searchButtonAttributes: {
87
+ type: Object,
88
+ required: false,
89
+ default: () => ({})
85
90
  }
86
91
  },
87
92
 
@@ -159,7 +164,7 @@ var script = {
159
164
  const __vue_script__ = script;
160
165
 
161
166
  /* template */
162
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-form-input-group',{staticClass:"gl-search-box-by-click",scopedSlots:_vm._u([(_vm.historyItems)?{key:"prepend",fn:function(){return [_c('gl-dropdown',{ref:"historyDropdown",staticClass:"gl-search-box-by-click-history",attrs:{"menu-class":"gl-search-box-by-click-menu","category":"secondary","disabled":_vm.disabled},scopedSlots:_vm._u([{key:"button-content",fn:function(){return [_c('gl-icon',{staticClass:"gl-search-box-by-click-history-icon",attrs:{"name":"history"}}),_vm._v(" "),_c('gl-icon',{staticClass:"gl-search-box-by-click-history-icon-chevron",attrs:{"name":"chevron-down"}}),_vm._v(" "),_c('span',{staticClass:"gl-sr-only"},[_vm._v("Toggle history")])]},proxy:true}],null,false,2220989388)},[_vm._v(" "),_c('gl-dropdown-text',{staticClass:"gl-search-box-by-click-history-header"},[_vm._v("\n "+_vm._s(_vm.recentSearchesHeader)+"\n "),_c('gl-button',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip.hover",value:({ container: _vm.tooltipContainer }),expression:"{ container: tooltipContainer }",modifiers:{"hover":true}}],ref:"closeHistory",staticClass:"gl-search-box-by-click-close-history-button",attrs:{"title":_vm.closeButtonTitle,"aria-label":_vm.closeButtonTitle,"category":"tertiary","name":"close","icon":"close"},on:{"click":_vm.closeHistoryDropdown}})],1),_vm._v(" "),_c('gl-dropdown-divider'),_vm._v(" "),(_vm.historyItems.length)?[_vm._l((_vm.historyItems),function(item,idx){return _c('gl-dropdown-item',{key:idx,staticClass:"gl-search-box-by-click-history-item",on:{"click":function($event){return _vm.selectHistoryItem(item)}}},[_vm._t("history-item",[_vm._v(_vm._s(item))],{"historyItem":item})],2)}),_vm._v(" "),_c('gl-dropdown-divider'),_vm._v(" "),_c('gl-dropdown-item',{ref:"clearHistory",on:{"click":function($event){return _vm.$emit('clear-history')}}},[_vm._v(_vm._s(_vm.clearRecentSearchesText))])]:_c('gl-dropdown-text',{staticClass:"gl-search-box-by-click-history-no-searches"},[_vm._v(_vm._s(_vm.noRecentSearchesText))])],2)]},proxy:true}:null,{key:"append",fn:function(){return [_c('gl-button',{ref:"searchButton",staticClass:"gl-search-box-by-click-search-button",attrs:{"icon":"search","disabled":_vm.disabled,"aria-label":"Search"},on:{"click":function($event){return _vm.search(_vm.currentValue)}}})]},proxy:true}],null,true)},[_vm._v(" "),_vm._t("input",[_c('gl-form-input',_vm._b({ref:"input",staticClass:"gl-search-box-by-click-input",attrs:{"disabled":_vm.disabled},on:{"focus":function($event){_vm.isFocused = true;},"blur":function($event){_vm.isFocused = false;},"keydown":function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"enter",13,$event.key,"Enter")){ return null; }return _vm.search(_vm.currentValue)}},model:{value:(_vm.currentValue),callback:function ($$v) {_vm.currentValue=$$v;},expression:"currentValue"}},'gl-form-input',_vm.inputAttributes,false))]),_vm._v(" "),(_vm.clearable && _vm.hasValue && !_vm.disabled)?_c('gl-clear-icon-button',{staticClass:"gl-search-box-by-click-icon-button gl-search-box-by-click-clear-button gl-clear-icon-button",attrs:{"title":_vm.clearButtonTitle,"tooltip-container":_vm.tooltipContainer,"data-testid":"filtered-search-clear-button"},on:{"click":_vm.clearInput}}):_vm._e()],2)};
167
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-form-input-group',{staticClass:"gl-search-box-by-click",scopedSlots:_vm._u([(_vm.historyItems)?{key:"prepend",fn:function(){return [_c('gl-dropdown',{ref:"historyDropdown",staticClass:"gl-search-box-by-click-history",attrs:{"menu-class":"gl-search-box-by-click-menu","category":"secondary","disabled":_vm.disabled},scopedSlots:_vm._u([{key:"button-content",fn:function(){return [_c('gl-icon',{staticClass:"gl-search-box-by-click-history-icon",attrs:{"name":"history"}}),_vm._v(" "),_c('gl-icon',{staticClass:"gl-search-box-by-click-history-icon-chevron",attrs:{"name":"chevron-down"}}),_vm._v(" "),_c('span',{staticClass:"gl-sr-only"},[_vm._v("Toggle history")])]},proxy:true}],null,false,2220989388)},[_vm._v(" "),_c('gl-dropdown-text',{staticClass:"gl-search-box-by-click-history-header"},[_vm._v("\n "+_vm._s(_vm.recentSearchesHeader)+"\n "),_c('gl-button',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip.hover",value:({ container: _vm.tooltipContainer }),expression:"{ container: tooltipContainer }",modifiers:{"hover":true}}],ref:"closeHistory",staticClass:"gl-search-box-by-click-close-history-button",attrs:{"title":_vm.closeButtonTitle,"aria-label":_vm.closeButtonTitle,"category":"tertiary","name":"close","icon":"close"},on:{"click":_vm.closeHistoryDropdown}})],1),_vm._v(" "),_c('gl-dropdown-divider'),_vm._v(" "),(_vm.historyItems.length)?[_vm._l((_vm.historyItems),function(item,idx){return _c('gl-dropdown-item',{key:idx,staticClass:"gl-search-box-by-click-history-item",on:{"click":function($event){return _vm.selectHistoryItem(item)}}},[_vm._t("history-item",[_vm._v(_vm._s(item))],{"historyItem":item})],2)}),_vm._v(" "),_c('gl-dropdown-divider'),_vm._v(" "),_c('gl-dropdown-item',{ref:"clearHistory",on:{"click":function($event){return _vm.$emit('clear-history')}}},[_vm._v(_vm._s(_vm.clearRecentSearchesText))])]:_c('gl-dropdown-text',{staticClass:"gl-search-box-by-click-history-no-searches"},[_vm._v(_vm._s(_vm.noRecentSearchesText))])],2)]},proxy:true}:null,{key:"append",fn:function(){return [_c('gl-button',_vm._b({ref:"searchButton",staticClass:"gl-search-box-by-click-search-button",attrs:{"icon":"search","disabled":_vm.disabled,"aria-label":"Search","data-testid":"search-button"},on:{"click":function($event){return _vm.search(_vm.currentValue)}}},'gl-button',_vm.searchButtonAttributes,false))]},proxy:true}],null,true)},[_vm._v(" "),_vm._t("input",[_c('gl-form-input',_vm._b({ref:"input",staticClass:"gl-search-box-by-click-input",attrs:{"disabled":_vm.disabled},on:{"focus":function($event){_vm.isFocused = true;},"blur":function($event){_vm.isFocused = false;},"keydown":function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"enter",13,$event.key,"Enter")){ return null; }return _vm.search(_vm.currentValue)}},model:{value:(_vm.currentValue),callback:function ($$v) {_vm.currentValue=$$v;},expression:"currentValue"}},'gl-form-input',_vm.inputAttributes,false))]),_vm._v(" "),(_vm.clearable && _vm.hasValue && !_vm.disabled)?_c('gl-clear-icon-button',{staticClass:"gl-search-box-by-click-icon-button gl-search-box-by-click-clear-button gl-clear-icon-button",attrs:{"title":_vm.clearButtonTitle,"tooltip-container":_vm.tooltipContainer,"data-testid":"filtered-search-clear-button"},on:{"click":_vm.clearInput}}):_vm._e()],2)};
163
168
  var __vue_staticRenderFns__ = [];
164
169
 
165
170
  /* style */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "36.0.0",
3
+ "version": "36.1.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -35,6 +35,12 @@ export default {
35
35
  additionalInfo:
36
36
  'Additional classes to add to the suggestion list menu. NOTE: this not reactive, and the value must be available and fixed when the component is instantiated',
37
37
  },
38
+ searchButtonAttributes: {
39
+ additionalInfo: 'HTML attributes to add to the search button',
40
+ },
41
+ searchInputAttributes: {
42
+ additionalInfo: 'HTML attributes to add to the search input',
43
+ },
38
44
  },
39
45
  events: [
40
46
  {
@@ -1,4 +1,4 @@
1
- import Vue from 'vue';
1
+ import Vue, { nextTick } from 'vue';
2
2
  import { shallowMount, mount } from '@vue/test-utils';
3
3
  import GlFilteredSearch from './filtered_search.vue';
4
4
  import GlFilteredSearchSuggestion from './filtered_search_suggestion.vue';
@@ -31,7 +31,7 @@ describe('Filtered search', () => {
31
31
  stubs: {
32
32
  GlSearchBoxByClick: {
33
33
  name: 'GlSearchBoxByClickStub',
34
- props: ['clearable'],
34
+ props: ['clearable', 'searchButtonAttributes'],
35
35
  template: '<div><slot name="input"></slot></div>',
36
36
  },
37
37
  },
@@ -66,7 +66,7 @@ describe('Filtered search', () => {
66
66
  createComponent({
67
67
  value: [{ type: 'faketoken', value: { data: '' } }],
68
68
  });
69
- await wrapper.vm.$nextTick();
69
+ await nextTick();
70
70
 
71
71
  expect(findSearchBox().props('clearable')).toBe(true);
72
72
  });
@@ -75,7 +75,7 @@ describe('Filtered search', () => {
75
75
  createComponent({
76
76
  value: ['one', 'two'],
77
77
  });
78
- await wrapper.vm.$nextTick();
78
+ await nextTick();
79
79
 
80
80
  const inputEventArgs = wrapper.emitted().input[1][0];
81
81
  expect(inputEventArgs.every((t) => t.type === TERM_TOKEN_TYPE)).toBe(true);
@@ -86,7 +86,7 @@ describe('Filtered search', () => {
86
86
  createComponent({
87
87
  value: ['one two'],
88
88
  });
89
- await wrapper.vm.$nextTick();
89
+ await nextTick();
90
90
 
91
91
  const inputEventArgs = wrapper.emitted().input[1][0];
92
92
  expect(inputEventArgs.every((t) => t.type === TERM_TOKEN_TYPE)).toBe(true);
@@ -104,7 +104,7 @@ describe('Filtered search', () => {
104
104
  `('passes through $eventName', async ({ eventName, payload }) => {
105
105
  createComponent();
106
106
  findSearchBox().vm.$emit(eventName, payload[0]);
107
- await wrapper.vm.$nextTick();
107
+ await nextTick();
108
108
 
109
109
  expect(wrapper.emitted()[eventName][0]).toStrictEqual(payload);
110
110
  });
@@ -113,11 +113,11 @@ describe('Filtered search', () => {
113
113
  createComponent({
114
114
  value: [{ type: 'faketoken', value: '' }],
115
115
  });
116
- await wrapper.vm.$nextTick();
116
+ await nextTick();
117
117
 
118
118
  wrapper.findComponent(FakeToken).vm.$emit('activate');
119
119
 
120
- await wrapper.vm.$nextTick();
120
+ await nextTick();
121
121
 
122
122
  expect(wrapper.findComponent(FakeToken).props('active')).toBe(true);
123
123
  });
@@ -126,12 +126,12 @@ describe('Filtered search', () => {
126
126
  createComponent({
127
127
  value: [{ type: 'faketoken', value: '' }],
128
128
  });
129
- await wrapper.vm.$nextTick();
129
+ await nextTick();
130
130
 
131
131
  wrapper.findComponent(FakeToken).vm.$emit('activate');
132
132
  wrapper.findComponent(FakeToken).vm.$emit('deactivate');
133
133
 
134
- await wrapper.vm.$nextTick();
134
+ await nextTick();
135
135
 
136
136
  expect(
137
137
  wrapper.findAllComponents({ ref: 'tokens' }).filter((w) => w.props('active') === true)
@@ -145,12 +145,12 @@ describe('Filtered search', () => {
145
145
  { type: 'faketoken', value: { data: '2' } },
146
146
  ],
147
147
  });
148
- await wrapper.vm.$nextTick();
148
+ await nextTick();
149
149
 
150
150
  wrapper.findComponent(FakeToken).vm.$emit('activate');
151
151
  wrapper.findAllComponents(FakeToken).at(1).vm.$emit('deactivate');
152
152
 
153
- await wrapper.vm.$nextTick();
153
+ await nextTick();
154
154
 
155
155
  expect(wrapper.findComponent(FakeToken).props('active')).toBe(true);
156
156
  });
@@ -161,16 +161,16 @@ describe('Filtered search', () => {
161
161
  value: [{ type: 'faketoken', value: { data: '' } }, 'one', 'two', 'three'],
162
162
  });
163
163
 
164
- await wrapper.vm.$nextTick();
164
+ await nextTick();
165
165
 
166
166
  findSecondTerm().vm.$emit('activate');
167
167
  findSecondTerm().vm.$emit('input', { data: '' });
168
168
 
169
- await wrapper.vm.$nextTick();
169
+ await nextTick();
170
170
 
171
171
  findSecondTerm().vm.$emit('deactivate');
172
172
 
173
- await wrapper.vm.$nextTick();
173
+ await nextTick();
174
174
 
175
175
  expect(wrapper.emitted().input.pop()[0]).toStrictEqual([
176
176
  { type: 'faketoken', value: { data: '' } },
@@ -184,11 +184,11 @@ describe('Filtered search', () => {
184
184
  createComponent({
185
185
  value: [{ type: 'faketoken', value: '' }, 'one'],
186
186
  });
187
- await wrapper.vm.$nextTick();
187
+ await nextTick();
188
188
 
189
189
  wrapper.findComponent(FakeToken).vm.$emit('destroy');
190
190
 
191
- await wrapper.vm.$nextTick();
191
+ await nextTick();
192
192
 
193
193
  expect(wrapper.emitted().input.pop()[0]).toStrictEqual([
194
194
  { type: TERM_TOKEN_TYPE, value: { data: 'one' } },
@@ -200,11 +200,11 @@ describe('Filtered search', () => {
200
200
  createComponent({
201
201
  value: ['one', { type: 'faketoken', value: '' }, 'two'],
202
202
  });
203
- await wrapper.vm.$nextTick();
203
+ await nextTick();
204
204
 
205
205
  wrapper.findComponent(FakeToken).vm.$emit('destroy');
206
206
 
207
- await wrapper.vm.$nextTick();
207
+ await nextTick();
208
208
 
209
209
  expect(wrapper.findComponent(GlFilteredSearchTerm).props('active')).toBe(true);
210
210
  });
@@ -213,7 +213,7 @@ describe('Filtered search', () => {
213
213
  createComponent();
214
214
  wrapper.findComponent(GlFilteredSearchTerm).vm.$emit('destroy');
215
215
 
216
- await wrapper.vm.$nextTick();
216
+ await nextTick();
217
217
 
218
218
  expect(wrapper.emitted().input.pop()[0]).toStrictEqual([
219
219
  { type: TERM_TOKEN_TYPE, value: { data: '' } },
@@ -226,7 +226,7 @@ describe('Filtered search', () => {
226
226
  .findComponent(GlFilteredSearchTerm)
227
227
  .vm.$emit('replace', { type: 'faketoken', value: { data: 'test' } });
228
228
 
229
- await wrapper.vm.$nextTick();
229
+ await nextTick();
230
230
 
231
231
  expect(wrapper.emitted().input.pop()[0]).toStrictEqual([
232
232
  { type: 'faketoken', value: { data: 'test' } },
@@ -241,13 +241,13 @@ describe('Filtered search', () => {
241
241
 
242
242
  findSearchBox().vm.$emit('input', '');
243
243
 
244
- await wrapper.vm.$nextTick();
244
+ await nextTick();
245
245
 
246
246
  wrapper
247
247
  .findComponent(GlFilteredSearchTerm)
248
248
  .vm.$emit('replace', { type: 'faketoken', value: { data: 'test' } });
249
249
 
250
- await wrapper.vm.$nextTick();
250
+ await nextTick();
251
251
 
252
252
  expect(wrapper.emitted().input.pop()[0]).toStrictEqual([
253
253
  { type: 'faketoken', value: { data: 'test' } },
@@ -260,7 +260,7 @@ describe('Filtered search', () => {
260
260
  wrapper.findComponent(GlFilteredSearchTerm).vm.$emit('activate');
261
261
  wrapper.findComponent(GlFilteredSearchTerm).vm.$emit('split');
262
262
 
263
- await wrapper.vm.$nextTick();
263
+ await nextTick();
264
264
 
265
265
  expect(wrapper.emitted().input.pop()[0]).toStrictEqual([
266
266
  { type: TERM_TOKEN_TYPE, value: { data: 'one' } },
@@ -270,12 +270,12 @@ describe('Filtered search', () => {
270
270
 
271
271
  it('jumps to last token when insert of empty term requested', async () => {
272
272
  createComponent({ value: ['one', 'two'] });
273
- await wrapper.vm.$nextTick();
273
+ await nextTick();
274
274
 
275
275
  wrapper.findComponent(GlFilteredSearchTerm).vm.$emit('activate');
276
276
  wrapper.findComponent(GlFilteredSearchTerm).vm.$emit('split');
277
277
 
278
- await wrapper.vm.$nextTick();
278
+ await nextTick();
279
279
 
280
280
  expect(wrapper.findAllComponents(GlFilteredSearchTerm).at(2).props('active')).toBe(true);
281
281
  expect(wrapper.emitted().input.pop()[0]).toStrictEqual([
@@ -289,11 +289,11 @@ describe('Filtered search', () => {
289
289
  createComponent({ value: ['one'] });
290
290
  wrapper.findComponent(GlFilteredSearchTerm).vm.$emit('activate');
291
291
 
292
- await wrapper.vm.$nextTick();
292
+ await nextTick();
293
293
 
294
294
  wrapper.findComponent(GlFilteredSearchTerm).vm.$emit('split', ['foo', 'bar']);
295
295
 
296
- await wrapper.vm.$nextTick();
296
+ await nextTick();
297
297
 
298
298
  expect(wrapper.emitted().input.pop()[0]).toStrictEqual([
299
299
  { type: TERM_TOKEN_TYPE, value: { data: 'one' } },
@@ -336,7 +336,7 @@ describe('Filtered search', () => {
336
336
  createComponent({
337
337
  value: [{ type: 'faketoken', value: '' }],
338
338
  });
339
- await wrapper.vm.$nextTick();
339
+ await nextTick();
340
340
 
341
341
  const fakeTokenInstance = wrapper.findComponent(FakeToken);
342
342
  expect(fakeTokenInstance.exists()).toBe(true);
@@ -344,6 +344,40 @@ describe('Filtered search', () => {
344
344
  expect.arrayContaining(['current-value', 'index', 'config', 'value'])
345
345
  );
346
346
  });
347
+
348
+ it('passes `searchButtonAttributes` prop to `GlSearchBoxByClick`', () => {
349
+ const searchButtonAttributes = { 'data-qa-selector': 'foo-bar' };
350
+
351
+ createComponent({ searchButtonAttributes });
352
+
353
+ expect(findSearchBox().props('searchButtonAttributes')).toEqual(searchButtonAttributes);
354
+ });
355
+
356
+ it('passes `searchInputAttributes` prop to search term', async () => {
357
+ const searchInputAttributes = { 'data-qa-selector': 'foo-bar' };
358
+
359
+ createComponent({
360
+ value: ['one'],
361
+ searchInputAttributes,
362
+ });
363
+ await nextTick();
364
+
365
+ expect(wrapper.findComponent(GlFilteredSearchTerm).props('searchInputAttributes')).toEqual(
366
+ searchInputAttributes
367
+ );
368
+ });
369
+
370
+ it('passes `isLastToken` prop to search terms', async () => {
371
+ createComponent({
372
+ value: ['one'],
373
+ });
374
+ await nextTick();
375
+
376
+ const filteredSearchTerms = wrapper.findAllComponents(GlFilteredSearchTerm);
377
+
378
+ expect(filteredSearchTerms.at(0).props('isLastToken')).toBe(false);
379
+ expect(filteredSearchTerms.at(1).props('isLastToken')).toBe(true);
380
+ });
347
381
  });
348
382
 
349
383
  describe('Filtered search integration tests', () => {
@@ -420,7 +454,7 @@ describe('Filtered search integration tests', () => {
420
454
  beforeEach(async () => {
421
455
  mountComponent();
422
456
  activate(0);
423
- await wrapper.vm.$nextTick();
457
+ await nextTick();
424
458
  });
425
459
 
426
460
  it('brings focus to term element input', () => {
@@ -441,7 +475,7 @@ describe('Filtered search integration tests', () => {
441
475
  const input = findInput();
442
476
  input.setValue('sta'); // partial of "static"
443
477
 
444
- await wrapper.vm.$nextTick();
478
+ await nextTick();
445
479
 
446
480
  const suggestions = wrapper.findComponent(GlFilteredSearchSuggestionList);
447
481
  expect(suggestions.exists()).toBe(true);
@@ -452,7 +486,7 @@ describe('Filtered search integration tests', () => {
452
486
  const input = findInput();
453
487
  input.setValue('--wrong-- ');
454
488
 
455
- await wrapper.vm.$nextTick();
489
+ await nextTick();
456
490
 
457
491
  const suggestions = wrapper.findComponent(GlFilteredSearchSuggestionList);
458
492
  expect(suggestions.exists()).toBe(true);
@@ -463,7 +497,7 @@ describe('Filtered search integration tests', () => {
463
497
  const input = findInput();
464
498
  input.setValue('--wrong--');
465
499
 
466
- await wrapper.vm.$nextTick();
500
+ await nextTick();
467
501
 
468
502
  const suggestions = wrapper.findComponent(GlFilteredSearchSuggestionList);
469
503
  expect(suggestions.exists()).toBe(false);
@@ -473,11 +507,11 @@ describe('Filtered search integration tests', () => {
473
507
  const input = findInput();
474
508
  input.trigger('keydown', { key: 'ArrowDown' });
475
509
 
476
- await wrapper.vm.$nextTick();
510
+ await nextTick();
477
511
 
478
512
  input.trigger('keydown', { key: 'Enter' });
479
513
 
480
- await wrapper.vm.$nextTick();
514
+ await nextTick();
481
515
 
482
516
  const token = wrapper.findComponent(GlFilteredSearchToken);
483
517
  expect(token.exists()).toBe(true);
@@ -488,9 +522,9 @@ describe('Filtered search integration tests', () => {
488
522
 
489
523
  const input = findInput();
490
524
  input.trigger('keydown', { key: 'ArrowDown' });
491
- await wrapper.vm.$nextTick();
525
+ await nextTick();
492
526
  input.trigger('keydown', { key: 'Enter' });
493
- await wrapper.vm.$nextTick();
527
+ await nextTick();
494
528
 
495
529
  expect(wrapper.props('value')).toEqual(initialValue);
496
530
  });
@@ -500,11 +534,11 @@ describe('Filtered search integration tests', () => {
500
534
  input.trigger('keydown', { key: 'ArrowDown' });
501
535
  const alignSuggestionsSpy = jest.spyOn(wrapper.vm, 'alignSuggestions');
502
536
 
503
- await wrapper.vm.$nextTick();
537
+ await nextTick();
504
538
 
505
539
  input.trigger('keydown', { key: 'Enter' });
506
540
 
507
- await wrapper.vm.$nextTick();
541
+ await nextTick();
508
542
 
509
543
  expect(alignSuggestionsSpy).toHaveBeenCalled();
510
544
  });
@@ -514,7 +548,7 @@ describe('Filtered search integration tests', () => {
514
548
  mountComponent({ value: ['token', { type: 'unique', value: { data: 'something' } }] });
515
549
  activate(0);
516
550
 
517
- await wrapper.vm.$nextTick();
551
+ await nextTick();
518
552
 
519
553
  const suggestions = wrapper.findComponent(GlFilteredSearchSuggestionList);
520
554
  expect(suggestions.exists()).toBe(true);
@@ -525,27 +559,27 @@ describe('Filtered search integration tests', () => {
525
559
  mountComponent({ value: ['one two'] });
526
560
  activate(0);
527
561
 
528
- await wrapper.vm.$nextTick();
562
+ await nextTick();
529
563
 
530
564
  activate(1);
531
565
 
532
- await wrapper.vm.$nextTick();
566
+ await nextTick();
533
567
 
534
568
  expect(wrapper.findAllComponents(GlFilteredSearchTerm).at(1).find('input').exists()).toBe(true);
535
569
  });
536
570
 
537
571
  it('correctly switches focus on token destroy', async () => {
538
572
  mountComponent({ value: ['one t three'] });
539
- await wrapper.vm.$nextTick();
573
+ await nextTick();
540
574
 
541
575
  activate(1);
542
576
 
543
- await wrapper.vm.$nextTick();
577
+ await nextTick();
544
578
 
545
579
  // Unfortunately backspace is not working in JSDOM
546
580
  wrapper.findAllComponents(GlFilteredSearchTerm).at(1).vm.$emit('destroy');
547
581
 
548
- await wrapper.vm.$nextTick();
582
+ await nextTick();
549
583
 
550
584
  expect(document.activeElement).toBe(
551
585
  wrapper.findComponent(GlFilteredSearchTerm).find('input').element
@@ -554,14 +588,14 @@ describe('Filtered search integration tests', () => {
554
588
 
555
589
  it('clicking clear button clears component input', async () => {
556
590
  mountComponent({ value: ['one two three'] });
557
- await wrapper.vm.$nextTick();
591
+ await nextTick();
558
592
 
559
593
  wrapper
560
594
  .findAll('button')
561
595
  .filter((b) => b.attributes('name') === 'clear')
562
596
  .trigger('click');
563
597
 
564
- await wrapper.vm.$nextTick();
598
+ await nextTick();
565
599
 
566
600
  expect(wrapper.findAllComponents(GlFilteredSearchTerm)).toHaveLength(1);
567
601
  });
@@ -85,6 +85,16 @@ export default {
85
85
  required: false,
86
86
  default: false,
87
87
  },
88
+ searchButtonAttributes: {
89
+ type: Object,
90
+ required: false,
91
+ default: () => ({}),
92
+ },
93
+ searchInputAttributes: {
94
+ type: Object,
95
+ required: false,
96
+ default: () => ({}),
97
+ },
88
98
  },
89
99
  data() {
90
100
  return {
@@ -253,6 +263,7 @@ export default {
253
263
  :value="tokens"
254
264
  :history-items="historyItems"
255
265
  :clearable="hasValue"
266
+ :search-button-attributes="searchButtonAttributes"
256
267
  data-testid="filtered-search-input"
257
268
  @submit="submit"
258
269
  @input="applyNewValue"
@@ -278,6 +289,8 @@ export default {
278
289
  :index="idx"
279
290
  :placeholder="termPlaceholder"
280
291
  :show-friendly-text="showFriendlyText"
292
+ :search-input-attributes="searchInputAttributes"
293
+ :is-last-token="isLastToken(idx)"
281
294
  class="gl-filtered-search-item"
282
295
  :class="{
283
296
  'gl-filtered-search-last-item': isLastToken(idx),
@@ -13,6 +13,12 @@ export default {
13
13
  value: {
14
14
  additionalInfo: 'Current term value',
15
15
  },
16
+ searchInputAttributes: {
17
+ additionalInfo: 'HTML attributes to add to the search input',
18
+ },
19
+ isLastToken: {
20
+ additionalInfo: 'If this is the last token',
21
+ },
16
22
  },
17
23
  events: [
18
24
  { event: 'activate', description: 'Emitted when this term token is clicked' },
@@ -1,3 +1,4 @@
1
+ import { nextTick } from 'vue';
1
2
  import { shallowMount } from '@vue/test-utils';
2
3
  import GlFilteredSearchSuggestion from './filtered_search_suggestion.vue';
3
4
  import FilteredSearchTerm from './filtered_search_term.vue';
@@ -11,6 +12,8 @@ const availableTokens = [
11
12
  describe('Filtered search term', () => {
12
13
  let wrapper;
13
14
 
15
+ const searchInputAttributes = { 'data-qa-selector': 'foo-bar' };
16
+
14
17
  const defaultProps = {
15
18
  availableTokens: [],
16
19
  };
@@ -18,6 +21,7 @@ describe('Filtered search term', () => {
18
21
  const segmentStub = {
19
22
  name: 'gl-filtered-search-token-segment-stub',
20
23
  template: '<div><slot name="view"></slot><slot name="suggestions"></slot></div>',
24
+ props: ['searchInputAttributes', 'isLastToken'],
21
25
  };
22
26
 
23
27
  const createComponent = (props) => {
@@ -29,6 +33,8 @@ describe('Filtered search term', () => {
29
33
  });
30
34
  };
31
35
 
36
+ const findTokenSegmentComponent = () => wrapper.findComponent(segmentStub);
37
+
32
38
  it('renders value in inactive mode', () => {
33
39
  createComponent({ value: { data: 'test-value' } });
34
40
  expect(wrapper.html()).toMatchSnapshot();
@@ -44,11 +50,12 @@ describe('Filtered search term', () => {
44
50
  expect(wrapper.find('input').attributes('placeholder')).toBe('placeholder-stub');
45
51
  });
46
52
 
47
- it('filters suggestions by input', () => {
53
+ it('filters suggestions by input', async () => {
48
54
  createComponent({ availableTokens, active: true, value: { data: 'test1' } });
49
- return wrapper.vm.$nextTick().then(() => {
50
- expect(wrapper.findAllComponents(GlFilteredSearchSuggestion)).toHaveLength(2);
51
- });
55
+
56
+ await nextTick();
57
+
58
+ expect(wrapper.findAllComponents(GlFilteredSearchSuggestion)).toHaveLength(2);
52
59
  });
53
60
 
54
61
  it.each`
@@ -61,14 +68,40 @@ describe('Filtered search term', () => {
61
68
  ${'backspace'} | ${'destroy'}
62
69
  `(
63
70
  'emits $emittedEvent when token segment emits $originalEvent',
64
- ({ originalEvent, emittedEvent }) => {
71
+ async ({ originalEvent, emittedEvent }) => {
65
72
  createComponent({ active: true, value: { data: 'something' } });
66
73
 
67
- wrapper.findComponent(segmentStub).vm.$emit(originalEvent);
74
+ findTokenSegmentComponent().vm.$emit(originalEvent);
75
+
76
+ await nextTick();
68
77
 
69
- return wrapper.vm.$nextTick().then(() => {
70
- expect(wrapper.emitted()[emittedEvent]).toHaveLength(1);
71
- });
78
+ expect(wrapper.emitted()[emittedEvent]).toHaveLength(1);
72
79
  }
73
80
  );
81
+
82
+ it('passes `searchInputAttributes` and `isLastToken` prop to `GlFilteredSearchTokenSegment`', () => {
83
+ const isLastToken = true;
84
+
85
+ createComponent({
86
+ value: { data: 'something' },
87
+ searchInputAttributes,
88
+ isLastToken,
89
+ });
90
+
91
+ expect(findTokenSegmentComponent().props()).toEqual({
92
+ searchInputAttributes,
93
+ isLastToken,
94
+ });
95
+ });
96
+
97
+ it('adds `searchInputAttributes` prop to search term input', () => {
98
+ createComponent({
99
+ placeholder: 'placeholder-stub',
100
+ searchInputAttributes,
101
+ });
102
+
103
+ expect(wrapper.find('input').attributes('data-qa-selector')).toBe(
104
+ searchInputAttributes['data-qa-selector']
105
+ );
106
+ });
74
107
  });
@@ -28,6 +28,16 @@ export default {
28
28
  required: false,
29
29
  default: '',
30
30
  },
31
+ searchInputAttributes: {
32
+ type: Object,
33
+ required: false,
34
+ default: () => ({}),
35
+ },
36
+ isLastToken: {
37
+ type: Boolean,
38
+ required: false,
39
+ default: false,
40
+ },
31
41
  },
32
42
  computed: {
33
43
  suggestedTokens() {
@@ -54,6 +64,8 @@ export default {
54
64
  class="gl-filtered-search-term-token"
55
65
  :active="active"
56
66
  :class="{ 'gl-w-full': placeholder }"
67
+ :search-input-attributes="searchInputAttributes"
68
+ :is-last-token="isLastToken"
57
69
  @activate="$emit('activate')"
58
70
  @deactivate="$emit('deactivate')"
59
71
  @complete="$emit('replace', { type: $event })"
@@ -74,6 +86,7 @@ export default {
74
86
  <template #view>
75
87
  <input
76
88
  v-if="placeholder"
89
+ v-bind="searchInputAttributes"
77
90
  class="gl-filtered-search-term-input"
78
91
  :placeholder="placeholder"
79
92
  :aria-label="placeholder"
@@ -19,6 +19,12 @@ export default {
19
19
  value: {
20
20
  additionalInfo: 'Current term value',
21
21
  },
22
+ searchInputAttributes: {
23
+ additionalInfo: 'HTML attributes to add to the search input',
24
+ },
25
+ isLastToken: {
26
+ additionalInfo: 'If this is the last token',
27
+ },
22
28
  },
23
29
  events: [
24
30
  { event: 'activate', description: 'Emitted on mousedown event on the main component' },
@@ -6,6 +6,8 @@ const OPTIONS = [{ value: '=' }, { value: '!=' }];
6
6
  describe('Filtered search token segment', () => {
7
7
  let wrapper;
8
8
 
9
+ const searchInputAttributes = { 'data-qa-selector': 'foo-bar' };
10
+
9
11
  beforeAll(() => {
10
12
  if (!HTMLElement.prototype.scrollIntoView) {
11
13
  HTMLElement.prototype.scrollIntoView = jest.fn();
@@ -251,4 +253,55 @@ describe('Filtered search token segment', () => {
251
253
  });
252
254
  });
253
255
  });
256
+
257
+ describe('when input is active', () => {
258
+ it('adds `searchInputAttributes` prop to search token segment input', () => {
259
+ createComponent({ active: true, value: 'something', searchInputAttributes });
260
+
261
+ expect(wrapper.find('input').attributes('data-qa-selector')).toBe(
262
+ searchInputAttributes['data-qa-selector']
263
+ );
264
+ });
265
+
266
+ it('does not add `searchInputAttributes` prop to search token segment', () => {
267
+ createComponent({
268
+ active: true,
269
+ value: 'something',
270
+ searchInputAttributes,
271
+ isLastToken: true,
272
+ });
273
+
274
+ expect(wrapper.attributes('data-qa-selector')).toBe(undefined);
275
+ });
276
+ });
277
+
278
+ describe('when input is not active', () => {
279
+ describe('when `isLastToken` prop is `true`', () => {
280
+ it('adds `searchInputAttributes` prop to search token segment', () => {
281
+ createComponent({
282
+ active: false,
283
+ value: 'something',
284
+ searchInputAttributes,
285
+ isLastToken: true,
286
+ });
287
+
288
+ expect(wrapper.attributes('data-qa-selector')).toBe(
289
+ searchInputAttributes['data-qa-selector']
290
+ );
291
+ });
292
+ });
293
+
294
+ describe('when `isLastToken` prop is `false`', () => {
295
+ it('does not add `searchInputAttributes` prop to search token segment', () => {
296
+ createComponent({
297
+ active: false,
298
+ value: 'something',
299
+ searchInputAttributes,
300
+ isLastToken: false,
301
+ });
302
+
303
+ expect(wrapper.attributes('data-qa-selector')).toBe(undefined);
304
+ });
305
+ });
306
+ });
254
307
  });
@@ -49,6 +49,16 @@ export default {
49
49
  required: true,
50
50
  validator: () => true,
51
51
  },
52
+ searchInputAttributes: {
53
+ type: Object,
54
+ required: false,
55
+ default: () => ({}),
56
+ },
57
+ isLastToken: {
58
+ type: Boolean,
59
+ required: false,
60
+ default: false,
61
+ },
52
62
  },
53
63
 
54
64
  data() {
@@ -244,6 +254,7 @@ export default {
244
254
 
245
255
  <template>
246
256
  <div
257
+ v-bind="isLastToken && !active && searchInputAttributes"
247
258
  class="gl-filtered-search-token-segment"
248
259
  :class="{ 'gl-filtered-search-token-segment-active': active }"
249
260
  data-testid="filtered-search-token-segment"
@@ -252,6 +263,7 @@ export default {
252
263
  <template v-if="active">
253
264
  <input
254
265
  ref="input"
266
+ v-bind="searchInputAttributes"
255
267
  v-model="inputValue"
256
268
  class="gl-filtered-search-token-segment-input"
257
269
  :aria-label="label"
@@ -52,6 +52,9 @@ export default {
52
52
  additionalInfo:
53
53
  'Container for tooltip. Valid values: DOM node, selector string or `false` for default',
54
54
  },
55
+ searchButtonAttributes: {
56
+ additionalInfo: 'HTML attributes to add to the search button',
57
+ },
55
58
  },
56
59
  events: [
57
60
  {
@@ -25,6 +25,7 @@ describe('search box by click component', () => {
25
25
  };
26
26
 
27
27
  const findClearIcon = () => wrapper.findComponent(ClearIcon);
28
+ const findSearchButton = () => wrapper.find('[data-testid="search-button"]');
28
29
 
29
30
  it('emits input event when input changes', async () => {
30
31
  createComponent({ value: 'somevalue' });
@@ -125,8 +126,8 @@ describe('search box by click component', () => {
125
126
  });
126
127
 
127
128
  it('displays disabled search button', () => {
128
- expect(wrapper.findComponent({ ref: 'searchButton' }).exists()).toBe(true);
129
- expect(wrapper.findComponent({ ref: 'searchButton' }).attributes('disabled')).toBe('true');
129
+ expect(findSearchButton().exists()).toBe(true);
130
+ expect(findSearchButton().attributes('disabled')).toBe('true');
130
131
  });
131
132
 
132
133
  it('does not render clear icon even with value', () => {
@@ -145,9 +146,19 @@ describe('search box by click component', () => {
145
146
 
146
147
  it('emits submit event when search button is pressed', async () => {
147
148
  createComponent({ value: 'some-input' });
148
- wrapper.findComponent({ ref: 'searchButton' }).vm.$emit('click');
149
+ findSearchButton().vm.$emit('click');
149
150
 
150
151
  await wrapper.vm.$nextTick();
151
152
  expect(wrapper.emitted().submit[0]).toEqual(['some-input']);
152
153
  });
154
+
155
+ it('adds `searchButtonAttributes` prop to search button', () => {
156
+ const searchButtonAttributes = { 'data-qa-selector': 'foo-bar' };
157
+
158
+ createComponent({ searchButtonAttributes });
159
+
160
+ expect(findSearchButton().attributes('data-qa-selector')).toBe(
161
+ searchButtonAttributes['data-qa-selector']
162
+ );
163
+ });
153
164
  });
@@ -84,6 +84,11 @@ export default {
84
84
  validator: (value) =>
85
85
  value === false || typeof value === 'string' || value instanceof HTMLElement,
86
86
  },
87
+ searchButtonAttributes: {
88
+ type: Object,
89
+ required: false,
90
+ default: () => ({}),
91
+ },
87
92
  },
88
93
  data() {
89
94
  return {
@@ -216,11 +221,13 @@ export default {
216
221
  />
217
222
  <template #append class="gl-search-box-by-click-input-group-control">
218
223
  <gl-button
224
+ v-bind="searchButtonAttributes"
219
225
  ref="searchButton"
220
226
  class="gl-search-box-by-click-search-button"
221
227
  icon="search"
222
228
  :disabled="disabled"
223
229
  aria-label="Search"
230
+ data-testid="search-button"
224
231
  @click="search(currentValue)"
225
232
  />
226
233
  </template>