@gitlab/ui 38.0.1 → 38.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 (61) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +1 -1
  3. package/dist/components/base/breadcrumb/breadcrumb.js +10 -5
  4. package/dist/components/base/{filtered_search/examples/filtered_search.single_unique.example.js → breadcrumb/breadcrumb_item.js} +32 -28
  5. package/dist/components/base/filtered_search/filtered_search.documentation.js +2 -66
  6. package/dist/components/base/filtered_search/filtered_search.js +38 -0
  7. package/dist/components/base/filtered_search/filtered_search_suggestion.documentation.js +2 -8
  8. package/dist/components/base/filtered_search/filtered_search_suggestion.js +4 -0
  9. package/dist/components/base/filtered_search/filtered_search_suggestion_list.documentation.js +2 -7
  10. package/dist/components/base/filtered_search/filtered_search_suggestion_list.js +4 -0
  11. package/dist/components/base/filtered_search/filtered_search_term.documentation.js +2 -44
  12. package/dist/components/base/filtered_search/filtered_search_term.js +37 -0
  13. package/dist/components/base/filtered_search/filtered_search_token.documentation.js +2 -31
  14. package/dist/components/base/filtered_search/filtered_search_token.js +49 -0
  15. package/dist/components/base/filtered_search/filtered_search_token_segment.documentation.js +2 -46
  16. package/dist/components/base/filtered_search/filtered_search_token_segment.js +48 -0
  17. package/dist/components/charts/series_label/series_label.js +6 -1
  18. package/documentation/documented_stories.js +6 -0
  19. package/package.json +9 -7
  20. package/src/components/base/breadcrumb/breadcrumb.spec.js +24 -10
  21. package/src/components/base/breadcrumb/breadcrumb.vue +11 -6
  22. package/src/components/base/breadcrumb/breadcrumb_item.spec.js +45 -0
  23. package/src/components/base/breadcrumb/breadcrumb_item.vue +43 -0
  24. package/src/components/base/filtered_search/filtered_search.documentation.js +0 -76
  25. package/src/components/base/filtered_search/filtered_search.md +3 -4
  26. package/src/components/base/filtered_search/filtered_search.stories.js +248 -13
  27. package/src/components/base/filtered_search/filtered_search.vue +45 -0
  28. package/src/components/base/filtered_search/filtered_search_suggestion.documentation.js +0 -6
  29. package/src/components/base/filtered_search/filtered_search_suggestion.md +1 -7
  30. package/src/components/base/filtered_search/filtered_search_suggestion.stories.js +26 -18
  31. package/src/components/base/filtered_search/filtered_search_suggestion.vue +5 -0
  32. package/src/components/base/filtered_search/filtered_search_suggestion_list.documentation.js +0 -5
  33. package/src/components/base/filtered_search/filtered_search_suggestion_list.md +1 -7
  34. package/src/components/base/filtered_search/filtered_search_suggestion_list.stories.js +33 -25
  35. package/src/components/base/filtered_search/filtered_search_suggestion_list.vue +5 -0
  36. package/src/components/base/filtered_search/filtered_search_term.documentation.js +0 -41
  37. package/src/components/base/filtered_search/filtered_search_term.md +0 -2
  38. package/src/components/base/filtered_search/filtered_search_term.stories.js +33 -26
  39. package/src/components/base/filtered_search/filtered_search_term.vue +54 -0
  40. package/src/components/base/filtered_search/filtered_search_token.documentation.js +0 -26
  41. package/src/components/base/filtered_search/filtered_search_token.md +1 -3
  42. package/src/components/base/filtered_search/filtered_search_token.stories.js +136 -132
  43. package/src/components/base/filtered_search/filtered_search_token.vue +63 -0
  44. package/src/components/base/filtered_search/filtered_search_token_segment.documentation.js +0 -43
  45. package/src/components/base/filtered_search/filtered_search_token_segment.md +0 -2
  46. package/src/components/base/filtered_search/filtered_search_token_segment.stories.js +86 -79
  47. package/src/components/base/filtered_search/filtered_search_token_segment.vue +42 -0
  48. package/src/components/base/form/form_radio/form_radio.spec.js +21 -8
  49. package/src/components/charts/series_label/series_label.stories.js +6 -3
  50. package/src/components/charts/series_label/series_label.vue +3 -0
  51. package/dist/components/base/filtered_search/examples/filtered_search.default.example.js +0 -422
  52. package/dist/components/base/filtered_search/examples/filtered_search.friendly.example.js +0 -423
  53. package/dist/components/base/filtered_search/examples/filtered_search.history.example.js +0 -91
  54. package/dist/components/base/filtered_search/examples/filtered_search.multi_select.example.js +0 -196
  55. package/dist/components/base/filtered_search/examples/index.js +0 -32
  56. package/src/components/base/filtered_search/examples/filtered_search.default.example.vue +0 -298
  57. package/src/components/base/filtered_search/examples/filtered_search.friendly.example.vue +0 -300
  58. package/src/components/base/filtered_search/examples/filtered_search.history.example.vue +0 -50
  59. package/src/components/base/filtered_search/examples/filtered_search.multi_select.example.vue +0 -132
  60. package/src/components/base/filtered_search/examples/filtered_search.single_unique.example.vue +0 -31
  61. package/src/components/base/filtered_search/examples/index.js +0 -38
@@ -15,6 +15,7 @@ const DEFAULT_OPERATORS = [
15
15
  ];
16
16
 
17
17
  export default {
18
+ name: 'GlFilteredSearchToken',
18
19
  components: {
19
20
  GlToken,
20
21
  GlFilteredSearchTokenSegment,
@@ -26,11 +27,17 @@ export default {
26
27
  required: false,
27
28
  default: () => [],
28
29
  },
30
+ /**
31
+ * Token configuration with available operators and options.
32
+ */
29
33
  config: {
30
34
  type: Object,
31
35
  required: false,
32
36
  default: () => ({}),
33
37
  },
38
+ /**
39
+ * Determines if the token is being edited or not.
40
+ */
34
41
  active: {
35
42
  type: Boolean,
36
43
  required: false,
@@ -41,11 +48,17 @@ export default {
41
48
  required: false,
42
49
  default: () => [],
43
50
  },
51
+ /**
52
+ * Current token value.
53
+ */
44
54
  value: {
45
55
  type: Object,
46
56
  required: false,
47
57
  default: () => ({ operator: '', data: '' }),
48
58
  },
59
+ /**
60
+ * Display operators' descriptions instead of their values (e.g., "is" instead of "=").
61
+ */
49
62
  showFriendlyText: {
50
63
  type: Boolean,
51
64
  required: false,
@@ -88,6 +101,12 @@ export default {
88
101
  value: {
89
102
  deep: true,
90
103
  handler(newValue) {
104
+ /**
105
+ * Emitted when the token changes its value.
106
+ *
107
+ * @event input
108
+ * @type {object} dataObj Object containing the update value.
109
+ */
91
110
  this.$emit('input', newValue);
92
111
  },
93
112
  },
@@ -101,6 +120,11 @@ export default {
101
120
  }
102
121
  } else if (this.value.data === '') {
103
122
  this.activeSegment = null;
123
+ /**
124
+ * Emitted when token is about to be destroyed.
125
+ *
126
+ * @event destroy
127
+ */
104
128
  this.$emit('destroy');
105
129
  }
106
130
  },
@@ -124,6 +148,11 @@ export default {
124
148
  this.activeSegment = segment;
125
149
 
126
150
  if (!this.active) {
151
+ /**
152
+ * Emitted when this term token is clicked.
153
+ *
154
+ * @event activate
155
+ */
127
156
  this.$emit('activate');
128
157
  }
129
158
  },
@@ -138,6 +167,10 @@ export default {
138
167
 
139
168
  replaceWithTermIfEmpty() {
140
169
  if (this.value.operator === '' && this.value.data === '') {
170
+ /**
171
+ * Emitted when this token is converted to another type
172
+ * @property {object} token Replacement token configuration
173
+ */
141
174
  this.$emit('replace', { type: TERM_TOKEN_TYPE, value: { data: this.config.title } });
142
175
  }
143
176
  },
@@ -147,6 +180,11 @@ export default {
147
180
 
148
181
  if (newTokenConfig === this.config) {
149
182
  this.$nextTick(() => {
183
+ /**
184
+ * Emitted when this term token will lose its focus.
185
+ *
186
+ * @event deactivate
187
+ */
150
188
  this.$emit('deactivate');
151
189
  });
152
190
  return;
@@ -193,6 +231,11 @@ export default {
193
231
  if (this.config.multiSelect) {
194
232
  this.$emit('input', { ...this.value, data: this.multiSelectValues.join(COMMA) });
195
233
  }
234
+ /**
235
+ * Emitted when the token entry has been completed.
236
+ *
237
+ * @event complete
238
+ */
196
239
  this.$emit('complete');
197
240
  },
198
241
 
@@ -208,6 +251,10 @@ export default {
208
251
 
209
252
  <template>
210
253
  <div class="gl-filtered-search-token" :class="{ 'gl-filtered-search-token-active': active }">
254
+ <!--
255
+ Emitted when the token is submitted.
256
+ @event submit
257
+ -->
211
258
  <gl-filtered-search-token-segment
212
259
  key="title-segment"
213
260
  :value="config.title"
@@ -261,6 +308,16 @@ export default {
261
308
  </template>
262
309
  </gl-filtered-search-token-segment>
263
310
  <!-- eslint-disable vue/no-mutating-props -->
311
+ <!--
312
+ Emitted when a suggestion has been selected.
313
+ @event select
314
+ @type {string} value The value of the selected suggestion.
315
+ -->
316
+ <!--
317
+ Emitted when Space is pressed in-between term text.
318
+ @event split
319
+ @property {array} newTokens Token configurations
320
+ -->
264
321
  <gl-filtered-search-token-segment
265
322
  v-if="hasDataOrDataSegmentIsCurrentlyActive"
266
323
  key="data-segment"
@@ -279,9 +336,11 @@ export default {
279
336
  >
280
337
  <!-- eslint-enable vue/no-mutating-props -->
281
338
  <template #suggestions>
339
+ <!-- @slot The suggestions (implemented with GlFilteredSearchSuggestion). -->
282
340
  <slot name="suggestions"></slot>
283
341
  </template>
284
342
  <template #view="{ inputValue }">
343
+ <!-- @slot Used to customize how the token is rendered. -->
285
344
  <slot
286
345
  name="view-token"
287
346
  v-bind="{
@@ -300,6 +359,10 @@ export default {
300
359
  @mousedown="destroyByClose"
301
360
  >
302
361
  <span class="gl-filtered-search-token-data-content">
362
+ <!--
363
+ @slot Template for token value in inactive state
364
+ @binding {array} suggestions Slot for rendering autocomplete suggestions when no options are provided.
365
+ -->
303
366
  <slot name="view" v-bind="{ inputValue }">{{ inputValue }}</slot>
304
367
  </span>
305
368
  </gl-token>
@@ -2,47 +2,4 @@ import * as description from './filtered_search_token_segment.md';
2
2
 
3
3
  export default {
4
4
  description,
5
- bootstrapComponent: null,
6
- propsInfo: {
7
- active: {
8
- additionalInfo: 'If this term token is currently active',
9
- },
10
- options: {
11
- additionalInfo: '',
12
- },
13
- optionTextField: {
14
- additionalInfo: '',
15
- },
16
- customInputKeydownHandler: {
17
- additionalInfo: '',
18
- },
19
- value: {
20
- additionalInfo: 'Current term value',
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
- },
28
- },
29
- events: [
30
- { event: 'activate', description: 'Emitted on mousedown event on the main component' },
31
- { event: 'backspace', description: 'Emitted when Backspace is pressed and the value is empty' },
32
- {
33
- event: 'complete',
34
- description: 'Emitted when suggestion is selected from the suggestion list',
35
- },
36
- { event: 'submit', description: 'Emitted when Enter is pressed and no suggestion is selected' },
37
- {
38
- event: 'split',
39
- args: [
40
- {
41
- arg: 'newStrings',
42
- description: '(Array of strings) New strings to be converted into term tokens',
43
- },
44
- ],
45
- description: 'Emitted when Space appears in token segment value',
46
- },
47
- ],
48
5
  };
@@ -1,5 +1,3 @@
1
- # Filtered Search Token Segment
2
-
3
1
  The filtered search token segment is a component for managing token input either via free typing
4
2
  or by selecting item through dropdown list
5
3
 
@@ -1,8 +1,6 @@
1
- import { withKnobs, boolean } from '@storybook/addon-knobs';
2
1
  import PortalVue from 'portal-vue';
3
2
  import Vue from 'vue';
4
3
  import { GlFilteredSearchSuggestion } from '../../../index';
5
- import { documentedStoriesOf } from '../../../../documentation/documented_stories';
6
4
  import { provide } from './common_story_options';
7
5
  import readme from './filtered_search_term.md';
8
6
  import GlFilteredSearchTokenSegment from './filtered_search_token_segment.vue';
@@ -14,84 +12,93 @@ const staticOptions = [
14
12
  { icon: 'eye', value: false, title: 'No' },
15
13
  ];
16
14
 
17
- documentedStoriesOf('base/filtered-search/token-segment', readme)
18
- .addDecorator(withKnobs)
19
- .add('default', () => ({
20
- components: {
21
- GlFilteredSearchTokenSegment,
22
- GlFilteredSearchSuggestion,
23
- },
24
- provide,
25
- props: {
26
- active: {
27
- type: Boolean,
28
- default: boolean('active', true),
29
- },
30
- },
31
- data() {
32
- return {
33
- value: 'demo1',
34
- };
35
- },
36
- mounted() {
37
- this.$nextTick(() => document.activeElement.blur());
38
- },
39
- template: `
15
+ const generateProps = ({ active = true } = {}) => ({
16
+ active,
17
+ });
18
+
19
+ // eslint-disable-next-line no-unused-vars
20
+ export const Default = (args, { argTypes }) => ({
21
+ components: {
22
+ GlFilteredSearchTokenSegment,
23
+ GlFilteredSearchSuggestion,
24
+ },
25
+ provide,
26
+ props: ['active'],
27
+ data() {
28
+ return {
29
+ value: 'demo1',
30
+ };
31
+ },
32
+ mounted() {
33
+ this.$nextTick(() => document.activeElement.blur());
34
+ },
35
+ template: `
36
+ <div>
37
+ <div>v-model value: {{ value }} </div>
38
+ <div class="gl-border-1 gl-border-solid gl-border-gray-200">
39
+ <gl-filtered-search-token-segment
40
+ v-model="value"
41
+ class="gl-h-full"
42
+ :active="active"
43
+ >
44
+ <template #suggestions>
45
+ <gl-filtered-search-suggestion value="demo1">Static suggestion 1</gl-filtered-search-suggestion>
46
+ <gl-filtered-search-suggestion value="demo2">Static suggestion 2</gl-filtered-search-suggestion>
47
+ </template>
48
+ </gl-filtered-search-token-segment>
49
+ </div>
40
50
  <div>
41
- <div>v-model value: {{ value }} </div>
42
- <div class="gl-border-1 gl-border-solid gl-border-gray-200">
43
- <gl-filtered-search-token-segment
44
- v-model="value"
45
- class="gl-h-full"
46
- :active="active"
47
- >
48
- <template #suggestions>
49
- <gl-filtered-search-suggestion value="demo1">Static suggestion 1</gl-filtered-search-suggestion>
50
- <gl-filtered-search-suggestion value="demo2">Static suggestion 2</gl-filtered-search-suggestion>
51
- </template>
52
- </gl-filtered-search-token-segment>
53
- </div>
54
- <div>
55
- <portal-target name="portal" class="gl-relative" />
56
- </div>
51
+ <portal-target name="portal" class="gl-relative" />
52
+ </div>
53
+ </div>
54
+ `,
55
+ });
56
+ Default.args = generateProps();
57
+
58
+ // eslint-disable-next-line no-unused-vars
59
+ export const WithStaticOptions = (args, { argTypes }) => ({
60
+ components: {
61
+ GlFilteredSearchTokenSegment,
62
+ },
63
+ provide,
64
+ props: ['active'],
65
+ data() {
66
+ return {
67
+ value: true,
68
+ staticOptions,
69
+ };
70
+ },
71
+ mounted() {
72
+ this.$nextTick(() => document.activeElement.blur());
73
+ },
74
+ template: `
75
+ <div>
76
+ <div>v-model value: {{ value }} </div>
77
+ <div class="gl-border-1 gl-border-solid gl-border-gray-200">
78
+ <gl-filtered-search-token-segment
79
+ v-model="value"
80
+ class="gl-h-full"
81
+ :active="active"
82
+ :options="staticOptions"
83
+ option-text-field="title"
84
+ />
57
85
  </div>
58
- `,
59
- }))
60
- .add('with static options', () => ({
61
- components: {
62
- GlFilteredSearchTokenSegment,
63
- },
64
- provide,
65
- props: {
66
- active: {
67
- type: Boolean,
68
- default: boolean('active', true),
69
- },
70
- },
71
- data() {
72
- return {
73
- value: true,
74
- staticOptions,
75
- };
76
- },
77
- mounted() {
78
- this.$nextTick(() => document.activeElement.blur());
79
- },
80
- template: `
81
86
  <div>
82
- <div>v-model value: {{ value }} </div>
83
- <div class="gl-border-1 gl-border-solid gl-border-gray-200">
84
- <gl-filtered-search-token-segment
85
- v-model="value"
86
- class="gl-h-full"
87
- :active="active"
88
- :options="staticOptions"
89
- option-text-field="title"
90
- />
91
- </div>
92
- <div>
93
- <portal-target name="portal" class="gl-relative" />
94
- </div>
87
+ <portal-target name="portal" class="gl-relative" />
95
88
  </div>
96
- `,
97
- }));
89
+ </div>
90
+ `,
91
+ });
92
+ WithStaticOptions.args = generateProps();
93
+
94
+ export default {
95
+ title: 'base/filtered-search/token-segment',
96
+ component: GlFilteredSearchTokenSegment,
97
+ parameters: {
98
+ docs: {
99
+ description: {
100
+ component: readme,
101
+ },
102
+ },
103
+ },
104
+ };
@@ -7,6 +7,7 @@ import GlFilteredSearchSuggestionList from './filtered_search_suggestion_list.vu
7
7
  import { splitOnQuotes, wrapTokenInQuotes } from './filtered_search_utils';
8
8
 
9
9
  export default {
10
+ name: 'GlFilteredSearchTokenSegment',
10
11
  components: {
11
12
  Portal,
12
13
  GlFilteredSearchSuggestionList,
@@ -15,6 +16,9 @@ export default {
15
16
  inject: ['portalName', 'alignSuggestions'],
16
17
  inheritAttrs: false,
17
18
  props: {
19
+ /**
20
+ * If this token segment is currently being edited.
21
+ */
18
22
  active: {
19
23
  type: Boolean,
20
24
  required: false,
@@ -45,15 +49,24 @@ export default {
45
49
  required: false,
46
50
  default: () => () => false,
47
51
  },
52
+ /**
53
+ * Current term value
54
+ */
48
55
  value: {
49
56
  required: true,
50
57
  validator: () => true,
51
58
  },
59
+ /**
60
+ * HTML attributes to add to the search input
61
+ */
52
62
  searchInputAttributes: {
53
63
  type: Object,
54
64
  required: false,
55
65
  default: () => ({}),
56
66
  },
67
+ /**
68
+ * If this is the last token
69
+ */
57
70
  isLastToken: {
58
71
  type: Boolean,
59
72
  required: false,
@@ -89,6 +102,11 @@ export default {
89
102
  },
90
103
 
91
104
  set(v) {
105
+ /**
106
+ * Emitted when this token segment's value changes.
107
+ *
108
+ * @type {object} option The current option.
109
+ */
92
110
  this.$emit('input', this.getMatchingOptionForInputValue(v)?.value ?? v);
93
111
  },
94
112
  },
@@ -145,6 +163,10 @@ export default {
145
163
  this.$emit('input', this.getMatchingOptionForInputValue(firstWord)?.value ?? firstWord);
146
164
 
147
165
  if (otherWords.length) {
166
+ /**
167
+ * Emitted when Space appears in token segment value
168
+ * @property {array|string} newStrings New strings to be converted into term tokens
169
+ */
148
170
  this.$emit('split', otherWords);
149
171
  }
150
172
  },
@@ -153,6 +175,9 @@ export default {
153
175
  methods: {
154
176
  emitIfInactive(e) {
155
177
  if (!this.active) {
178
+ /**
179
+ * Emitted on mousedown event on the main component.
180
+ */
156
181
  this.$emit('activate');
157
182
  e.preventDefault();
158
183
  }
@@ -190,6 +215,11 @@ export default {
190
215
  applySuggestion(suggestedValue) {
191
216
  const formattedSuggestedValue = wrapTokenInQuotes(suggestedValue);
192
217
 
218
+ /**
219
+ * Emitted when autocomplete entry is selected.
220
+ *
221
+ * @type {string} value The selected value.
222
+ */
193
223
  this.$emit('select', formattedSuggestedValue);
194
224
 
195
225
  if (!this.multiSelect) {
@@ -205,6 +235,9 @@ export default {
205
235
  if (key === 'Backspace') {
206
236
  if (this.inputValue === '') {
207
237
  e.preventDefault();
238
+ /**
239
+ * Emitted when Backspace is pressed and the value is empty
240
+ */
208
241
  this.$emit('backspace');
209
242
  }
210
243
  return;
@@ -216,6 +249,9 @@ export default {
216
249
  if (suggestedValue != null) {
217
250
  this.applySuggestion(suggestedValue);
218
251
  } else {
252
+ /**
253
+ * Emitted when Enter is pressed and no suggestion is selected
254
+ */
219
255
  this.$emit('submit');
220
256
  }
221
257
  },
@@ -227,6 +263,9 @@ export default {
227
263
  },
228
264
  Escape: () => {
229
265
  e.preventDefault();
266
+ /**
267
+ * Emitted when suggestion is selected from the suggestion list
268
+ */
230
269
  this.$emit('complete');
231
270
  },
232
271
  };
@@ -258,6 +297,9 @@ export default {
258
297
  if (this.multiSelect) {
259
298
  this.$emit('complete');
260
299
  } else if (this.active) {
300
+ /**
301
+ * Emitted when this term token will lose its focus.
302
+ */
261
303
  this.$emit('deactivate');
262
304
  }
263
305
  },
@@ -5,6 +5,8 @@ import GlFormRadio from './form_radio.vue';
5
5
  describe('GlFormRadio', () => {
6
6
  let wrapper;
7
7
  let options;
8
+ let eventHandlers;
9
+
8
10
  const firstOption = {
9
11
  text: 'One',
10
12
  value: 'one',
@@ -16,6 +18,10 @@ describe('GlFormRadio', () => {
16
18
 
17
19
  const createWrapper = () => {
18
20
  options = [firstOption, secondOption];
21
+ eventHandlers = {
22
+ input: jest.fn(),
23
+ change: jest.fn(),
24
+ };
19
25
 
20
26
  wrapper = mount({
21
27
  data() {
@@ -32,9 +38,15 @@ describe('GlFormRadio', () => {
32
38
  :key="option.value"
33
39
  v-model="selected"
34
40
  :value="option.value"
41
+ @change="changeHandler"
42
+ @input="inputHandler"
35
43
  >{{ option.text }}</gl-form-radio>
36
44
  </div>
37
45
  `,
46
+ methods: {
47
+ changeHandler: eventHandlers.change,
48
+ inputHandler: eventHandlers.input,
49
+ },
38
50
  });
39
51
  };
40
52
 
@@ -79,16 +91,17 @@ describe('GlFormRadio', () => {
79
91
  });
80
92
 
81
93
  it('emits an input event on each radio, and a change event on the newly selected radio', () => {
82
- const [formRadioOne, formRadioTwo] = wrapper.findAllComponents(GlFormRadio).wrappers;
94
+ const { input, change } = eventHandlers;
95
+ const { value: clickedRadioValue } = secondOption;
83
96
 
84
- expect(formRadioOne.emitted()).toEqual({
85
- input: [[secondOption.value]],
86
- });
97
+ // The input handler is called twice because each radio emits one input event.
98
+ expect(input).toHaveBeenCalledTimes(2);
99
+ expect(input).toHaveBeenNthCalledWith(1, clickedRadioValue);
100
+ expect(input).toHaveBeenNthCalledWith(2, clickedRadioValue);
87
101
 
88
- expect(formRadioTwo.emitted()).toEqual({
89
- input: [[secondOption.value]],
90
- change: [[secondOption.value]],
91
- });
102
+ // The change handler is only called once, since only the newly selected radio emits a change event.
103
+ expect(change).toHaveBeenCalledTimes(1);
104
+ expect(change).toHaveBeenCalledWith(clickedRadioValue);
92
105
  });
93
106
 
94
107
  it('updates the bound value', () => {
@@ -35,14 +35,13 @@ const Template = (_args, { argTypes }) => ({
35
35
  });
36
36
 
37
37
  export const Default = Template.bind({});
38
- Default.args = generateProps({ color: '' });
38
+ Default.args = generateProps();
39
39
 
40
40
  export const WithLongName = Template.bind({});
41
- WithLongName.args = generateProps({ color: '', text: SERIES_NAME[SERIES_NAME_LONG] });
41
+ WithLongName.args = generateProps({ text: SERIES_NAME[SERIES_NAME_LONG] });
42
42
 
43
43
  export const WithLongNameWithNoSpaces = Template.bind({});
44
44
  WithLongNameWithNoSpaces.args = generateProps({
45
- color: '',
46
45
  text: SERIES_NAME[SERIES_NAME_LONG_WITHOUT_SPACES],
47
46
  });
48
47
 
@@ -72,6 +71,10 @@ export default {
72
71
  },
73
72
  },
74
73
  argTypes: {
74
+ type: {
75
+ control: 'select',
76
+ options: ['solid', 'dashed'],
77
+ },
75
78
  color: {
76
79
  control: 'color',
77
80
  },
@@ -12,6 +12,9 @@ export default {
12
12
  type: String,
13
13
  required: false,
14
14
  default: 'solid',
15
+ validator(value) {
16
+ return ['solid', 'dashed'].indexOf(value) !== -1;
17
+ },
15
18
  },
16
19
  },
17
20
  data() {