@gitlab/ui 34.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 (153) hide show
  1. package/CHANGELOG.md +45 -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/nav/nav.documentation.js +2 -7
  9. package/dist/components/base/search_box_by_click/search_box_by_click.documentation.js +3 -0
  10. package/dist/components/base/search_box_by_click/search_box_by_click.js +6 -1
  11. package/dist/components/base/tabs/tabs/tabs.documentation.js +1 -1
  12. package/dist/components/base/tabs/tabs/tabs.js +2 -12
  13. package/dist/components/utilities/truncate/truncate.documentation.js +2 -17
  14. package/dist/components/utilities/truncate/truncate.js +11 -0
  15. package/dist/index.css +1 -1
  16. package/dist/index.css.map +1 -1
  17. package/dist/utility_classes.css +1 -1
  18. package/dist/utility_classes.css.map +1 -1
  19. package/documentation/all_components.js +2 -2
  20. package/documentation/components_documentation.js +0 -2
  21. package/documentation/documented_stories.js +4 -0
  22. package/package.json +7 -10
  23. package/src/charts.js +19 -0
  24. package/src/components/base/alert/alert.stories.js +1 -1
  25. package/src/components/base/avatar/avatar.stories.js +8 -0
  26. package/src/components/base/avatar_labeled/avatar_labeled.stories.js +7 -0
  27. package/src/components/base/avatar_link/avatar_link.stories.js +7 -0
  28. package/src/components/base/avatars_inline/avatars_inline.stories.js +1 -1
  29. package/src/components/base/badge/badge.stories.js +115 -10
  30. package/src/components/base/banner/banner.stories.js +1 -1
  31. package/src/components/base/breadcrumb/breadcrumb.stories.js +1 -1
  32. package/src/components/base/broadcast_message/broadcast_message.stories.js +3 -3
  33. package/src/components/base/button/button.stories.js +11 -11
  34. package/src/components/base/button_group/button_group.stories.js +1 -1
  35. package/src/components/base/collapse/collapse.stories.js +1 -1
  36. package/src/components/base/drawer/drawer.stories.js +1 -1
  37. package/src/components/base/dropdown/dropdown.stories.js +5 -3
  38. package/src/components/base/dropdown/dropdown_divider.stories.js +1 -1
  39. package/src/components/base/dropdown/dropdown_form.stories.js +1 -1
  40. package/src/components/base/dropdown/dropdown_item.stories.js +1 -1
  41. package/src/components/base/dropdown/dropdown_section_header.stories.js +1 -1
  42. package/src/components/base/dropdown/dropdown_text.stories.js +1 -1
  43. package/src/components/base/filtered_search/filtered_search.documentation.js +6 -0
  44. package/src/components/base/filtered_search/filtered_search.spec.js +81 -47
  45. package/src/components/base/filtered_search/filtered_search.stories.js +11 -1
  46. package/src/components/base/filtered_search/filtered_search.vue +13 -0
  47. package/src/components/base/filtered_search/filtered_search_suggestion.stories.js +2 -0
  48. package/src/components/base/filtered_search/filtered_search_suggestion_list.stories.js +2 -0
  49. package/src/components/base/filtered_search/filtered_search_term.documentation.js +6 -0
  50. package/src/components/base/filtered_search/filtered_search_term.spec.js +42 -9
  51. package/src/components/base/filtered_search/filtered_search_term.vue +13 -0
  52. package/src/components/base/filtered_search/filtered_search_token.stories.js +2 -0
  53. package/src/components/base/filtered_search/filtered_search_token_segment.documentation.js +6 -0
  54. package/src/components/base/filtered_search/filtered_search_token_segment.spec.js +53 -0
  55. package/src/components/base/filtered_search/filtered_search_token_segment.stories.js +2 -0
  56. package/src/components/base/filtered_search/filtered_search_token_segment.vue +12 -0
  57. package/src/components/base/form/form.stories.js +1 -1
  58. package/src/components/base/form/form_checkbox/form_checkbox.stories.js +1 -1
  59. package/src/components/base/form/form_group/form_group.stories.js +6 -5
  60. package/src/components/base/form/form_input/form_input.stories.js +1 -1
  61. package/src/components/base/form/form_input_group/form_input_group.stories.js +1 -1
  62. package/src/components/base/form/form_radio/form_radio.stories.js +1 -1
  63. package/src/components/base/form/form_radio_group/form_radio_group.stories.js +2 -1
  64. package/src/components/base/form/form_select/form_select.stories.js +1 -1
  65. package/src/components/base/form/form_text/form_text.vue +3 -1
  66. package/src/components/base/form/form_textarea/form_textarea.stories.js +1 -1
  67. package/src/components/base/icon/icon.stories.js +1 -1
  68. package/src/components/base/infinite_scroll/infinite_scroll.stories.js +1 -1
  69. package/src/components/base/label/label.stories.js +1 -1
  70. package/src/components/base/link/link.stories.js +1 -1
  71. package/src/components/base/loading_icon/loading_icon.stories.js +2 -1
  72. package/src/components/base/modal/modal.scss +5 -2
  73. package/src/components/base/modal/modal.stories.js +1 -1
  74. package/src/components/base/nav/nav.documentation.js +0 -4
  75. package/src/components/base/nav/nav.md +8 -5
  76. package/src/components/base/nav/nav.stories.js +52 -15
  77. package/src/components/base/navbar/navbar.stories.js +1 -1
  78. package/src/components/base/paginated_list/paginated_list.stories.js +1 -1
  79. package/src/components/base/pagination/pagination.stories.js +7 -0
  80. package/src/components/base/path/path.stories.js +2 -2
  81. package/src/components/base/popover/popover.stories.js +6 -0
  82. package/src/components/base/progress_bar/progress_bar.stories.js +1 -1
  83. package/src/components/base/search_box_by_click/search_box_by_click.documentation.js +3 -0
  84. package/src/components/base/search_box_by_click/search_box_by_click.spec.js +14 -3
  85. package/src/components/base/search_box_by_click/search_box_by_click.stories.js +1 -1
  86. package/src/components/base/search_box_by_click/search_box_by_click.vue +7 -0
  87. package/src/components/base/search_box_by_type/search_box_by_type.stories.js +1 -1
  88. package/src/components/base/segmented_control/segmented_control.stories.js +1 -1
  89. package/src/components/base/skeleton_loader/skeleton_loader.stories.js +1 -1
  90. package/src/components/base/skeleton_loading/skeleton_loading.stories.js +1 -1
  91. package/src/components/base/sorting/sorting.stories.js +1 -1
  92. package/src/components/base/sorting/sorting_item.stories.js +1 -1
  93. package/src/components/base/table/table.stories.js +6 -1
  94. package/src/components/base/tabs/tabs/scrollable_tabs.spec.js +0 -1
  95. package/src/components/base/tabs/tabs/tabs.md +2 -2
  96. package/src/components/base/tabs/tabs/tabs.scss +3 -115
  97. package/src/components/base/tabs/tabs/tabs.spec.js +8 -5
  98. package/src/components/base/tabs/tabs/tabs.stories.js +9 -13
  99. package/src/components/base/tabs/tabs/tabs.vue +2 -11
  100. package/src/components/base/toast/toast.stories.js +6 -4
  101. package/src/components/base/toggle/toggle.stories.js +1 -1
  102. package/src/components/base/token/token.stories.js +2 -2
  103. package/src/components/base/tooltip/tooltip.stories.js +2 -1
  104. package/src/components/charts/area/area.stories.js +1 -1
  105. package/src/components/charts/bar/bar.stories.js +1 -1
  106. package/src/components/charts/chart/chart.stories.js +2 -2
  107. package/src/components/charts/column/column.stories.js +1 -1
  108. package/src/components/charts/discrete_scatter/discrete_scatter.stories.js +1 -1
  109. package/src/components/charts/heatmap/heatmap.stories.js +1 -1
  110. package/src/components/charts/legend/legend.stories.js +1 -1
  111. package/src/components/charts/line/line.stories.js +1 -1
  112. package/src/components/charts/series_label/series_label.stories.js +1 -1
  113. package/src/components/charts/single_stat/single_stat.stories.js +1 -1
  114. package/src/components/charts/sparkline/sparkline.stories.js +1 -1
  115. package/src/components/charts/stacked_column/stacked_column.stories.js +1 -1
  116. package/src/components/charts/tooltip/tooltip.stories.js +1 -1
  117. package/src/components/regions/dashboard_skeleton/dashboard_skeleton.stories.js +1 -1
  118. package/src/components/regions/empty_state/empty_state.stories.js +1 -1
  119. package/src/components/utilities/friendly_wrap/friendly_wrap.stories.js +1 -1
  120. package/src/components/utilities/intersection_observer/intersection_observer.stories.js +1 -1
  121. package/src/components/utilities/intersperse/intersperse.stories.js +1 -1
  122. package/src/components/utilities/sprintf/sprintf.stories.js +1 -1
  123. package/src/components/utilities/truncate/truncate.documentation.js +0 -14
  124. package/src/components/utilities/truncate/truncate.md +0 -4
  125. package/src/components/utilities/truncate/truncate.stories.js +38 -27
  126. package/src/components/utilities/truncate/truncate.vue +9 -0
  127. package/{config.js → src/config.js} +1 -1
  128. package/src/directives/hover_load/hover_load.stories.js +1 -1
  129. package/src/directives/outside/outside.stories.js +1 -1
  130. package/src/directives/resize_observer/resize_observer.stories.js +1 -1
  131. package/src/directives/safe_html/safe_html.stories.js +1 -1
  132. package/src/index.js +111 -0
  133. package/src/scss/utilities.scss +8 -0
  134. package/src/scss/utility-mixins/box-shadow.scss +4 -0
  135. package/{utils.js → src/utils.js} +1 -1
  136. package/charts.js +0 -19
  137. package/dist/components/base/nav/examples/index.js +0 -13
  138. package/dist/components/base/nav/examples/nav.basic.example.js +0 -38
  139. package/dist/components/base/nav/nav_item.documentation.js +0 -13
  140. package/dist/components/base/nav/nav_item_dropdown.documentation.js +0 -14
  141. package/dist/components/utilities/truncate/examples/index.js +0 -13
  142. package/dist/components/utilities/truncate/examples/truncate.basic.example.js +0 -38
  143. package/index.js +0 -111
  144. package/src/components/base/nav/examples/index.js +0 -15
  145. package/src/components/base/nav/examples/nav.basic.example.vue +0 -8
  146. package/src/components/base/nav/nav_item.documentation.js +0 -6
  147. package/src/components/base/nav/nav_item.md +0 -7
  148. package/src/components/base/nav/nav_item.stories.js +0 -23
  149. package/src/components/base/nav/nav_item_dropdown.documentation.js +0 -7
  150. package/src/components/base/nav/nav_item_dropdown.md +0 -7
  151. package/src/components/base/nav/nav_item_dropdown.stories.js +0 -51
  152. package/src/components/utilities/truncate/examples/index.js +0 -15
  153. package/src/components/utilities/truncate/examples/truncate.basic.example.vue +0 -5
@@ -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
  });
@@ -1,6 +1,13 @@
1
1
  import { withKnobs } from '@storybook/addon-knobs';
2
2
  import { documentedStoriesOf } from '../../../../documentation/documented_stories';
3
- import { GlFilteredSearch, GlFilteredSearchToken } from '../../../../index';
3
+ import {
4
+ GlFilteredSearch,
5
+ GlFilteredSearchToken,
6
+ GlFilteredSearchSuggestion,
7
+ GlLoadingIcon,
8
+ GlToken,
9
+ GlAvatar,
10
+ } from '../../../index';
4
11
  import { setStoryTimeout } from '../../../utils/test_utils';
5
12
  import readme from './filtered_search.md';
6
13
 
@@ -29,6 +36,7 @@ const fakeLabels = [
29
36
  ];
30
37
 
31
38
  const UserToken = {
39
+ components: { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon, GlAvatar },
32
40
  props: ['value', 'active'],
33
41
  inheritAttrs: false,
34
42
  data() {
@@ -104,6 +112,7 @@ const UserToken = {
104
112
  };
105
113
 
106
114
  const MilestoneToken = {
115
+ components: { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon },
107
116
  props: ['value', 'active'],
108
117
  inheritAttrs: false,
109
118
  data() {
@@ -163,6 +172,7 @@ const MilestoneToken = {
163
172
  };
164
173
 
165
174
  const LabelToken = {
175
+ components: { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon, GlToken },
166
176
  props: ['value', 'active'],
167
177
  inheritAttrs: false,
168
178
  data() {
@@ -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),
@@ -1,4 +1,5 @@
1
1
  import { withKnobs } from '@storybook/addon-knobs';
2
+ import { GlFilteredSearchSuggestion } from '../../../index';
2
3
  import { documentedStoriesOf } from '../../../../documentation/documented_stories';
3
4
  import readme from './filtered_search_suggestion.md';
4
5
 
@@ -7,6 +8,7 @@ const noop = () => {};
7
8
  documentedStoriesOf('base/filtered-search/suggestion', readme)
8
9
  .addDecorator(withKnobs)
9
10
  .add('default', () => ({
11
+ components: { GlFilteredSearchSuggestion },
10
12
  provide: {
11
13
  filteredSearchSuggestionListInstance: {
12
14
  register: noop,
@@ -1,4 +1,5 @@
1
1
  import { withKnobs } from '@storybook/addon-knobs';
2
+ import { GlFilteredSearchSuggestionList, GlFilteredSearchSuggestion } from '../../../index';
2
3
  import { documentedStoriesOf } from '../../../../documentation/documented_stories';
3
4
  import { provide } from './common_story_options';
4
5
  import readme from './filtered_search_suggestion_list.md';
@@ -6,6 +7,7 @@ import readme from './filtered_search_suggestion_list.md';
6
7
  documentedStoriesOf('base/filtered-search/suggestion-list', readme)
7
8
  .addDecorator(withKnobs)
8
9
  .add('default', () => ({
10
+ components: { GlFilteredSearchSuggestionList, GlFilteredSearchSuggestion },
9
11
  data() {
10
12
  return {
11
13
  iteration: 1,
@@ -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"
@@ -2,6 +2,7 @@ import { withKnobs, boolean } from '@storybook/addon-knobs';
2
2
  import PortalVue from 'portal-vue';
3
3
  import Vue from 'vue';
4
4
  import { documentedStoriesOf } from '../../../../documentation/documented_stories';
5
+ import { GlIcon } from '../../../index';
5
6
  import { provide } from './common_story_options';
6
7
  import GlFilteredSearchSuggestion from './filtered_search_suggestion.vue';
7
8
  import readme from './filtered_search_token.md';
@@ -15,6 +16,7 @@ documentedStoriesOf('base/filtered-search/token', readme)
15
16
  components: {
16
17
  GlFilteredSearchToken,
17
18
  GlFilteredSearchSuggestion,
19
+ GlIcon,
18
20
  },
19
21
  provide,
20
22
  props: {
@@ -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' },