@gitlab/ui 37.5.1 → 37.5.2

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [37.5.2](https://gitlab.com/gitlab-org/gitlab-ui/compare/v37.5.1...v37.5.2) (2022-03-11)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **GlFilteredSearch:** Token activation after destruction ([40b0513](https://gitlab.com/gitlab-org/gitlab-ui/commit/40b051307065ff29fea53ad65457d6b99f116c92))
7
+
1
8
  ## [37.5.1](https://gitlab.com/gitlab-org/gitlab-ui/compare/v37.5.0...v37.5.1) (2022-03-10)
2
9
 
3
10
 
@@ -5,7 +5,7 @@ import { GlTooltipDirective } from '../../../directives/tooltip';
5
5
  import GlIcon from '../icon/icon';
6
6
  import GlSearchBoxByClick from '../search_box_by_click/search_box_by_click';
7
7
  import GlFilteredSearchTerm from './filtered_search_term';
8
- import { TERM_TOKEN_TYPE, needDenormalization, denormalizeTokens, isEmptyTerm, normalizeTokens } from './filtered_search_utils';
8
+ import { TERM_TOKEN_TYPE, needDenormalization, denormalizeTokens, isEmptyTerm, INTENT_ACTIVATE_PREVIOUS, normalizeTokens } from './filtered_search_utils';
9
9
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
10
10
 
11
11
  Vue.use(PortalVue);
@@ -184,8 +184,8 @@ var script = {
184
184
  return ((_this$getTokenEntry = this.getTokenEntry(type)) === null || _this$getTokenEntry === void 0 ? void 0 : _this$getTokenEntry.token) || GlFilteredSearchTerm;
185
185
  },
186
186
 
187
- activate(token) {
188
- this.activeTokenIdx = token;
187
+ activate(idx) {
188
+ this.activeTokenIdx = idx;
189
189
  },
190
190
 
191
191
  alignSuggestions(ref) {
@@ -216,15 +216,31 @@ var script = {
216
216
  },
217
217
 
218
218
  destroyToken(idx) {
219
+ let {
220
+ intent
221
+ } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
222
+
219
223
  if (this.tokens.length === 1) {
220
224
  return;
221
225
  }
222
226
 
223
- this.tokens.splice(idx, 1);
227
+ this.tokens.splice(idx, 1); // First, attempt to honor the user's activation intent behind the
228
+ // destruction of the token, if any. Otherwise, try to maintain the
229
+ // active state for the token that was active at the time. If that's not
230
+ // possible, make sure no token is active.
231
+
232
+ if (intent === INTENT_ACTIVATE_PREVIOUS) {
233
+ // If there is a previous token, activate it; else, deactivate all tokens
234
+ this.activeTokenIdx = idx > 0 ? idx - 1 : null;
235
+ } else if (idx < this.activeTokenIdx) {
236
+ // Preserve the active token's active status (it shifted down one index)
237
+ this.activeTokenIdx -= 1;
238
+ } else if (idx === this.activeTokenIdx) {
239
+ // User destroyed the active token; don't activate another one.
240
+ this.activeTokenIdx = null;
241
+ } // Do nothing if there was no active token, or if idx > this.activeTokenIdx,
242
+ // to preserve the active state of the remaining tokens.
224
243
 
225
- if (idx !== 0) {
226
- this.activeTokenIdx = idx - 1;
227
- }
228
244
  },
229
245
 
230
246
  replaceToken(idx, token) {
@@ -281,7 +297,7 @@ const __vue_script__ = script;
281
297
  /* template */
282
298
  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:{
283
299
  'gl-filtered-search-last-item': _vm.isLastToken(idx),
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))};
300
+ },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, $event)},"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))};
285
301
  var __vue_staticRenderFns__ = [];
286
302
 
287
303
  /* style */
@@ -1,5 +1,6 @@
1
1
  import GlFilteredSearchSuggestion from './filtered_search_suggestion';
2
2
  import GlFilteredSearchTokenSegment from './filtered_search_token_segment';
3
+ import { INTENT_ACTIVATE_PREVIOUS } from './filtered_search_utils';
3
4
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
4
5
 
5
6
  var script = {
@@ -63,6 +64,14 @@ var script = {
63
64
  }
64
65
 
65
66
  }
67
+ },
68
+ methods: {
69
+ onBackspace() {
70
+ this.$emit('destroy', {
71
+ intent: INTENT_ACTIVATE_PREVIOUS
72
+ });
73
+ }
74
+
66
75
  }
67
76
  };
68
77
 
@@ -70,7 +79,7 @@ var script = {
70
79
  const __vue_script__ = script;
71
80
 
72
81
  /* template */
73
- 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,"current-value":_vm.currentValue},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)};
82
+ 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,"current-value":_vm.currentValue},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":_vm.onBackspace,"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)};
74
83
  var __vue_staticRenderFns__ = [];
75
84
 
76
85
  /* style */
@@ -3,6 +3,7 @@ import _last from 'lodash/last';
3
3
  import _first from 'lodash/first';
4
4
 
5
5
  const TERM_TOKEN_TYPE = 'filtered-search-term';
6
+ const INTENT_ACTIVATE_PREVIOUS = 'intent-activate-previous';
6
7
  function isEmptyTerm(token) {
7
8
  return token.type === TERM_TOKEN_TYPE && token.value.data.trim() === '';
8
9
  }
@@ -134,4 +135,4 @@ function wrapTokenInQuotes(token) {
134
135
  return `"${token}"`;
135
136
  }
136
137
 
137
- export { TERM_TOKEN_TYPE, denormalizeTokens, isEmptyTerm, needDenormalization, normalizeTokens, splitOnQuotes, wrapTokenInQuotes };
138
+ export { INTENT_ACTIVATE_PREVIOUS, TERM_TOKEN_TYPE, denormalizeTokens, isEmptyTerm, needDenormalization, normalizeTokens, splitOnQuotes, wrapTokenInQuotes };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "37.5.1",
3
+ "version": "37.5.2",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -83,7 +83,7 @@
83
83
  "@arkweid/lefthook": "^0.7.6",
84
84
  "@babel/core": "^7.10.2",
85
85
  "@babel/preset-env": "^7.10.2",
86
- "@gitlab/eslint-plugin": "10.0.2",
86
+ "@gitlab/eslint-plugin": "11.0.0",
87
87
  "@gitlab/stylelint-config": "4.0.0",
88
88
  "@gitlab/svgs": "2.6.0",
89
89
  "@rollup/plugin-commonjs": "^11.1.0",
@@ -5,7 +5,7 @@ import GlFilteredSearchSuggestion from './filtered_search_suggestion.vue';
5
5
  import GlFilteredSearchSuggestionList from './filtered_search_suggestion_list.vue';
6
6
  import GlFilteredSearchTerm from './filtered_search_term.vue';
7
7
  import GlFilteredSearchToken from './filtered_search_token.vue';
8
- import { TERM_TOKEN_TYPE } from './filtered_search_utils';
8
+ import { TERM_TOKEN_TYPE, INTENT_ACTIVATE_PREVIOUS } from './filtered_search_utils';
9
9
 
10
10
  jest.mock('~/directives/tooltip');
11
11
 
@@ -196,9 +196,99 @@ describe('Filtered search', () => {
196
196
  ]);
197
197
  });
198
198
 
199
- it('brings focus to previous token if current is destroyed', async () => {
199
+ it('makes previous token active if user intends it on token destruction', async () => {
200
200
  createComponent({
201
- value: ['one', { type: 'faketoken', value: '' }, 'two'],
201
+ value: [{ type: 'faketoken', value: '' }, ''],
202
+ });
203
+ await nextTick();
204
+
205
+ wrapper
206
+ .findComponent(GlFilteredSearchTerm)
207
+ .vm.$emit('destroy', { intent: INTENT_ACTIVATE_PREVIOUS });
208
+
209
+ await nextTick();
210
+
211
+ expect(wrapper.findComponent(FakeToken).props('active')).toBe(true);
212
+ });
213
+
214
+ it('makes no token active if user intends it on first token destruction', async () => {
215
+ createComponent({
216
+ value: ['foo', { type: 'faketoken', value: '' }],
217
+ });
218
+ await nextTick();
219
+ wrapper.findComponent(FakeToken).vm.$emit('activate');
220
+ await nextTick();
221
+
222
+ expect(wrapper.findComponent(FakeToken).props('active')).toBe(true);
223
+
224
+ wrapper
225
+ .findAllComponents(GlFilteredSearchTerm)
226
+ .at(0)
227
+ .vm.$emit('destroy', { intent: INTENT_ACTIVATE_PREVIOUS });
228
+
229
+ await nextTick();
230
+
231
+ expect(wrapper.findComponent(FakeToken).props('active')).toBe(false);
232
+ });
233
+
234
+ it('keeps active token active if later one destroyed', async () => {
235
+ createComponent({
236
+ value: [
237
+ { type: 'faketoken', value: '' },
238
+ { type: 'faketoken', value: '' },
239
+ { type: 'faketoken', value: '' },
240
+ ],
241
+ });
242
+ await nextTick();
243
+ wrapper.findAllComponents(FakeToken).at(0).vm.$emit('activate');
244
+ await nextTick();
245
+
246
+ wrapper.findAllComponents(FakeToken).at(2).vm.$emit('destroy');
247
+
248
+ await nextTick();
249
+
250
+ expect(wrapper.findAllComponents(FakeToken).at(0).props('active')).toBe(true);
251
+ });
252
+
253
+ it('keeps active token active if earlier one destroyed', async () => {
254
+ createComponent({
255
+ value: [
256
+ { type: 'faketoken', value: '' },
257
+ { type: 'faketoken', value: '' },
258
+ { type: 'faketoken', value: '' },
259
+ ],
260
+ });
261
+ await nextTick();
262
+ wrapper.findAllComponents(FakeToken).at(2).vm.$emit('activate');
263
+ await nextTick();
264
+
265
+ wrapper.findAllComponents(FakeToken).at(0).vm.$emit('destroy');
266
+
267
+ await nextTick();
268
+
269
+ expect(wrapper.findAllComponents(FakeToken).at(1).props('active')).toBe(true);
270
+ });
271
+
272
+ it('makes no token active if current is destroyed', async () => {
273
+ createComponent({
274
+ value: ['one', { type: 'faketoken', value: '' }],
275
+ });
276
+ await nextTick();
277
+ wrapper.findComponent(FakeToken).vm.$emit('activate');
278
+ await nextTick();
279
+
280
+ wrapper.findComponent(FakeToken).vm.$emit('destroy');
281
+
282
+ await nextTick();
283
+
284
+ wrapper.findAllComponents(GlFilteredSearchTerm).wrappers.forEach((searchTermWrapper) => {
285
+ expect(searchTermWrapper.props('active')).toBe(false);
286
+ });
287
+ });
288
+
289
+ it('keeps no token active if one was destroyed when none were active', async () => {
290
+ createComponent({
291
+ value: ['one', { type: 'faketoken', value: '' }],
202
292
  });
203
293
  await nextTick();
204
294
 
@@ -206,7 +296,7 @@ describe('Filtered search', () => {
206
296
 
207
297
  await nextTick();
208
298
 
209
- expect(wrapper.findComponent(GlFilteredSearchTerm).props('active')).toBe(true);
299
+ expect(wrapper.findComponent(GlFilteredSearchTerm).props('active')).toBe(false);
210
300
  });
211
301
 
212
302
  it('does not destroy last token', async () => {
@@ -580,22 +670,26 @@ describe('Filtered search integration tests', () => {
580
670
  expect(wrapper.findAllComponents(GlFilteredSearchTerm).at(1).find('input').exists()).toBe(true);
581
671
  });
582
672
 
583
- it('correctly switches focus on token destroy', async () => {
584
- mountComponent({ value: ['one t three'] });
673
+ it('activates previous token when backspacing on empty search term', async () => {
674
+ mountComponent({ value: ['zero one two'] });
585
675
  await nextTick();
586
676
 
587
677
  activate(1);
588
678
 
589
679
  await nextTick();
590
680
 
591
- // Unfortunately backspace is not working in JSDOM
592
- wrapper.findAllComponents(GlFilteredSearchTerm).at(1).vm.$emit('destroy');
681
+ // Make sure we have the expected search term
682
+ const inputWrapper = wrapper.find('input');
683
+ expect(inputWrapper.element.value).toBe('one');
593
684
 
594
- await nextTick();
685
+ // Mimic backspace behavior for jsdom
686
+ await inputWrapper.setValue('');
687
+ await inputWrapper.trigger('keydown', { key: 'Backspace' });
595
688
 
596
- expect(document.activeElement).toBe(
597
- wrapper.findComponent(GlFilteredSearchTerm).find('input').element
598
- );
689
+ // Make sure the previous token/search term is now active
690
+ const input = wrapper.find('input').element;
691
+ expect(input.value).toBe('zero');
692
+ expect(document.activeElement).toBe(input);
599
693
  });
600
694
 
601
695
  it('clicking clear button clears component input', async () => {
@@ -36,6 +36,7 @@ const fakeLabels = [
36
36
  ];
37
37
 
38
38
  const UserToken = {
39
+ name: 'UserToken',
39
40
  components: { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon, GlAvatar },
40
41
  props: ['value', 'active'],
41
42
  inheritAttrs: false,
@@ -112,6 +113,7 @@ const UserToken = {
112
113
  };
113
114
 
114
115
  const MilestoneToken = {
116
+ name: 'MilestoneToken',
115
117
  components: { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon },
116
118
  props: ['value', 'active'],
117
119
  inheritAttrs: false,
@@ -172,6 +174,7 @@ const MilestoneToken = {
172
174
  };
173
175
 
174
176
  const LabelToken = {
177
+ name: 'LabelToken',
175
178
  components: { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon, GlToken },
176
179
  props: ['value', 'active'],
177
180
  inheritAttrs: false,
@@ -9,6 +9,7 @@ import GlFilteredSearchTerm from './filtered_search_term.vue';
9
9
  import {
10
10
  isEmptyTerm,
11
11
  TERM_TOKEN_TYPE,
12
+ INTENT_ACTIVATE_PREVIOUS,
12
13
  normalizeTokens,
13
14
  denormalizeTokens,
14
15
  needDenormalization,
@@ -173,8 +174,8 @@ export default {
173
174
  return this.getTokenEntry(type)?.token || GlFilteredSearchTerm;
174
175
  },
175
176
 
176
- activate(token) {
177
- this.activeTokenIdx = token;
177
+ activate(idx) {
178
+ this.activeTokenIdx = idx;
178
179
  },
179
180
 
180
181
  alignSuggestions(ref) {
@@ -201,15 +202,29 @@ export default {
201
202
  this.activeTokenIdx = null;
202
203
  },
203
204
 
204
- destroyToken(idx) {
205
+ destroyToken(idx, { intent } = {}) {
205
206
  if (this.tokens.length === 1) {
206
207
  return;
207
208
  }
208
209
 
209
210
  this.tokens.splice(idx, 1);
210
- if (idx !== 0) {
211
- this.activeTokenIdx = idx - 1;
211
+
212
+ // First, attempt to honor the user's activation intent behind the
213
+ // destruction of the token, if any. Otherwise, try to maintain the
214
+ // active state for the token that was active at the time. If that's not
215
+ // possible, make sure no token is active.
216
+ if (intent === INTENT_ACTIVATE_PREVIOUS) {
217
+ // If there is a previous token, activate it; else, deactivate all tokens
218
+ this.activeTokenIdx = idx > 0 ? idx - 1 : null;
219
+ } else if (idx < this.activeTokenIdx) {
220
+ // Preserve the active token's active status (it shifted down one index)
221
+ this.activeTokenIdx -= 1;
222
+ } else if (idx === this.activeTokenIdx) {
223
+ // User destroyed the active token; don't activate another one.
224
+ this.activeTokenIdx = null;
212
225
  }
226
+ // Do nothing if there was no active token, or if idx > this.activeTokenIdx,
227
+ // to preserve the active state of the remaining tokens.
213
228
  },
214
229
 
215
230
  replaceToken(idx, token) {
@@ -297,7 +312,7 @@ export default {
297
312
  }"
298
313
  @activate="activate(idx)"
299
314
  @deactivate="deactivate(token)"
300
- @destroy="destroyToken(idx)"
315
+ @destroy="destroyToken(idx, $event)"
301
316
  @replace="replaceToken(idx, $event)"
302
317
  @complete="completeToken"
303
318
  @submit="submit"
@@ -2,6 +2,7 @@ import { nextTick } from 'vue';
2
2
  import { shallowMount } from '@vue/test-utils';
3
3
  import GlFilteredSearchSuggestion from './filtered_search_suggestion.vue';
4
4
  import FilteredSearchTerm from './filtered_search_term.vue';
5
+ import { INTENT_ACTIVATE_PREVIOUS } from './filtered_search_utils';
5
6
 
6
7
  const availableTokens = [
7
8
  { type: 'foo', title: 'test1-foo', token: 'stub', icon: 'eye' },
@@ -59,23 +60,27 @@ describe('Filtered search term', () => {
59
60
  });
60
61
 
61
62
  it.each`
62
- originalEvent | emittedEvent
63
- ${'activate'} | ${'activate'}
64
- ${'deactivate'} | ${'deactivate'}
65
- ${'split'} | ${'split'}
66
- ${'submit'} | ${'submit'}
67
- ${'complete'} | ${'replace'}
68
- ${'backspace'} | ${'destroy'}
63
+ originalEvent | emittedEvent | payload
64
+ ${'activate'} | ${'activate'} | ${undefined}
65
+ ${'deactivate'} | ${'deactivate'} | ${undefined}
66
+ ${'split'} | ${'split'} | ${undefined}
67
+ ${'submit'} | ${'submit'} | ${undefined}
68
+ ${'complete'} | ${'replace'} | ${{ type: undefined }}
69
+ ${'backspace'} | ${'destroy'} | ${{ intent: INTENT_ACTIVATE_PREVIOUS }}
69
70
  `(
70
71
  'emits $emittedEvent when token segment emits $originalEvent',
71
- async ({ originalEvent, emittedEvent }) => {
72
+ async ({ originalEvent, emittedEvent, payload }) => {
72
73
  createComponent({ active: true, value: { data: 'something' } });
73
74
 
74
75
  findTokenSegmentComponent().vm.$emit(originalEvent);
75
76
 
76
77
  await nextTick();
77
78
 
78
- expect(wrapper.emitted()[emittedEvent]).toHaveLength(1);
79
+ expect(wrapper.emitted(emittedEvent)).toHaveLength(1);
80
+
81
+ if (payload !== undefined) {
82
+ expect(wrapper.emitted(emittedEvent)[0][0]).toEqual(payload);
83
+ }
79
84
  }
80
85
  );
81
86
 
@@ -1,6 +1,7 @@
1
1
  <script>
2
2
  import GlFilteredSearchSuggestion from './filtered_search_suggestion.vue';
3
3
  import GlFilteredSearchTokenSegment from './filtered_search_token_segment.vue';
4
+ import { INTENT_ACTIVATE_PREVIOUS } from './filtered_search_utils';
4
5
 
5
6
  export default {
6
7
  components: {
@@ -59,6 +60,11 @@ export default {
59
60
  },
60
61
  },
61
62
  },
63
+ methods: {
64
+ onBackspace() {
65
+ this.$emit('destroy', { intent: INTENT_ACTIVATE_PREVIOUS });
66
+ },
67
+ },
62
68
  };
63
69
  </script>
64
70
 
@@ -75,7 +81,7 @@ export default {
75
81
  @activate="$emit('activate')"
76
82
  @deactivate="$emit('deactivate')"
77
83
  @complete="$emit('replace', { type: $event })"
78
- @backspace="$emit('destroy')"
84
+ @backspace="onBackspace"
79
85
  @submit="$emit('submit')"
80
86
  @split="$emit('split', $event)"
81
87
  >
@@ -2,6 +2,8 @@ import { first, last, isString } from 'lodash';
2
2
 
3
3
  export const TERM_TOKEN_TYPE = 'filtered-search-term';
4
4
 
5
+ export const INTENT_ACTIVATE_PREVIOUS = 'intent-activate-previous';
6
+
5
7
  export function isEmptyTerm(token) {
6
8
  return token.type === TERM_TOKEN_TYPE && token.value.data.trim() === '';
7
9
  }