@gitlab/ui 80.13.1 → 80.13.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.
Files changed (275) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/components/experimental/experiment_badge/experiment_badge.js +6 -6
  3. package/package.json +16 -12
  4. package/src/components/experimental/experiment_badge/experiment_badge.vue +12 -11
  5. package/src/components/base/accordion/accordion.spec.js +0 -58
  6. package/src/components/base/accordion/accordion.stories.js +0 -59
  7. package/src/components/base/accordion/accordion_item.spec.js +0 -131
  8. package/src/components/base/accordion/accordion_item.stories.js +0 -57
  9. package/src/components/base/alert/alert.spec.js +0 -229
  10. package/src/components/base/alert/alert.stories.js +0 -166
  11. package/src/components/base/avatar/avatar.spec.js +0 -91
  12. package/src/components/base/avatar/avatar.stories.js +0 -160
  13. package/src/components/base/avatar_labeled/avatar_labeled.spec.js +0 -109
  14. package/src/components/base/avatar_labeled/avatar_labeled.stories.js +0 -187
  15. package/src/components/base/avatar_link/avatar_link.stories.js +0 -86
  16. package/src/components/base/avatars_inline/avatars_inline.spec.js +0 -124
  17. package/src/components/base/avatars_inline/avatars_inline.stories.js +0 -83
  18. package/src/components/base/badge/badge.spec.js +0 -83
  19. package/src/components/base/badge/badge.stories.js +0 -184
  20. package/src/components/base/banner/banner.spec.js +0 -112
  21. package/src/components/base/banner/banner.stories.js +0 -103
  22. package/src/components/base/breadcrumb/breadcrumb.spec.js +0 -240
  23. package/src/components/base/breadcrumb/breadcrumb.stories.js +0 -100
  24. package/src/components/base/breadcrumb/breadcrumb_item.spec.js +0 -45
  25. package/src/components/base/broadcast_message/broadcast_message.spec.js +0 -32
  26. package/src/components/base/broadcast_message/broadcast_message.stories.js +0 -112
  27. package/src/components/base/button/button.spec.js +0 -225
  28. package/src/components/base/button/button.stories.js +0 -529
  29. package/src/components/base/button_group/button_group.stories.js +0 -132
  30. package/src/components/base/card/card.spec.js +0 -139
  31. package/src/components/base/card/card.stories.js +0 -48
  32. package/src/components/base/carousel/carousel.spec.js +0 -24
  33. package/src/components/base/carousel/carousel.stories.js +0 -51
  34. package/src/components/base/collapse/collapse.spec.js +0 -24
  35. package/src/components/base/collapse/collapse.stories.js +0 -42
  36. package/src/components/base/datepicker/datepicker.spec.js +0 -431
  37. package/src/components/base/datepicker/datepicker.stories.js +0 -149
  38. package/src/components/base/daterange_picker/daterange_picker.spec.js +0 -307
  39. package/src/components/base/daterange_picker/daterange_picker.stories.js +0 -145
  40. package/src/components/base/drawer/drawer.spec.js +0 -136
  41. package/src/components/base/drawer/drawer.stories.js +0 -272
  42. package/src/components/base/dropdown/dropdown.spec.js +0 -457
  43. package/src/components/base/dropdown/dropdown.stories.js +0 -574
  44. package/src/components/base/dropdown/dropdown_form.spec.js +0 -9
  45. package/src/components/base/dropdown/dropdown_item.spec.js +0 -67
  46. package/src/components/base/dropdown/dropdown_item.stories.js +0 -113
  47. package/src/components/base/filtered_search/__snapshots__/filtered_search_term.spec.js.snap +0 -31
  48. package/src/components/base/filtered_search/filtered_search.spec.js +0 -908
  49. package/src/components/base/filtered_search/filtered_search.stories.js +0 -678
  50. package/src/components/base/filtered_search/filtered_search_suggestion.spec.js +0 -67
  51. package/src/components/base/filtered_search/filtered_search_suggestion.stories.js +0 -33
  52. package/src/components/base/filtered_search/filtered_search_suggestion_list.spec.js +0 -337
  53. package/src/components/base/filtered_search/filtered_search_suggestion_list.stories.js +0 -53
  54. package/src/components/base/filtered_search/filtered_search_term.spec.js +0 -212
  55. package/src/components/base/filtered_search/filtered_search_term.stories.js +0 -60
  56. package/src/components/base/filtered_search/filtered_search_token.spec.js +0 -461
  57. package/src/components/base/filtered_search/filtered_search_token.stories.js +0 -199
  58. package/src/components/base/filtered_search/filtered_search_token_segment.spec.js +0 -393
  59. package/src/components/base/filtered_search/filtered_search_token_segment.stories.js +0 -110
  60. package/src/components/base/filtered_search/filtered_search_utils.spec.js +0 -82
  61. package/src/components/base/form/form.stories.js +0 -101
  62. package/src/components/base/form/form_checkbox/form_checkbox.stories.js +0 -55
  63. package/src/components/base/form/form_checkbox_tree/form_checkbox_tree.spec.js +0 -265
  64. package/src/components/base/form/form_checkbox_tree/form_checkbox_tree.stories.js +0 -134
  65. package/src/components/base/form/form_combobox/form_combobox.spec.js +0 -301
  66. package/src/components/base/form/form_combobox/form_combobox.stories.js +0 -116
  67. package/src/components/base/form/form_date/form_date.spec.js +0 -85
  68. package/src/components/base/form/form_date/form_date.stories.js +0 -108
  69. package/src/components/base/form/form_fields/form_field_validator.spec.js +0 -51
  70. package/src/components/base/form/form_fields/form_fields.spec.js +0 -531
  71. package/src/components/base/form/form_fields/form_fields.stories.js +0 -150
  72. package/src/components/base/form/form_fields/mappers.spec.js +0 -17
  73. package/src/components/base/form/form_fields/validators.spec.js +0 -56
  74. package/src/components/base/form/form_group/form_group.spec.js +0 -110
  75. package/src/components/base/form/form_group/form_group.stories.js +0 -123
  76. package/src/components/base/form/form_input/form_input.spec.js +0 -130
  77. package/src/components/base/form/form_input/form_input.stories.js +0 -128
  78. package/src/components/base/form/form_input_group/form_input_group.spec.js +0 -121
  79. package/src/components/base/form/form_input_group/form_input_group.stories.js +0 -86
  80. package/src/components/base/form/form_radio/form_radio.spec.js +0 -116
  81. package/src/components/base/form/form_radio/form_radio.stories.js +0 -70
  82. package/src/components/base/form/form_radio_group/form_radio_group.spec.js +0 -86
  83. package/src/components/base/form/form_radio_group/form_radio_group.stories.js +0 -79
  84. package/src/components/base/form/form_select/form_select.spec.js +0 -69
  85. package/src/components/base/form/form_select/form_select.stories.js +0 -156
  86. package/src/components/base/form/form_text/form_text.stories.js +0 -27
  87. package/src/components/base/form/form_textarea/form_textarea.spec.js +0 -242
  88. package/src/components/base/form/form_textarea/form_textarea.stories.js +0 -68
  89. package/src/components/base/form/input_group_text/input_group_text.spec.js +0 -9
  90. package/src/components/base/form/input_group_text/input_group_text.stories.js +0 -30
  91. package/src/components/base/icon/__snapshots__/icon.spec.js.snap +0 -14
  92. package/src/components/base/icon/icon.spec.js +0 -92
  93. package/src/components/base/icon/icon.stories.js +0 -56
  94. package/src/components/base/infinite_scroll/infinite_scroll.spec.js +0 -182
  95. package/src/components/base/infinite_scroll/infinite_scroll.stories.js +0 -94
  96. package/src/components/base/keyset_pagination/keyset_pagination.spec.js +0 -313
  97. package/src/components/base/keyset_pagination/keyset_pagination.stories.js +0 -66
  98. package/src/components/base/label/label.spec.js +0 -257
  99. package/src/components/base/label/label.stories.js +0 -76
  100. package/src/components/base/link/link.spec.js +0 -88
  101. package/src/components/base/link/link.stories.js +0 -64
  102. package/src/components/base/loading_icon/loading_icon.spec.js +0 -109
  103. package/src/components/base/loading_icon/loading_icon.stories.js +0 -60
  104. package/src/components/base/markdown/markdown.spec.js +0 -24
  105. package/src/components/base/markdown/markdown.stories.js +0 -33
  106. package/src/components/base/modal/modal.spec.js +0 -265
  107. package/src/components/base/modal/modal.stories.js +0 -197
  108. package/src/components/base/nav/nav.spec.js +0 -19
  109. package/src/components/base/nav/nav.stories.js +0 -57
  110. package/src/components/base/nav/nav_item.spec.js +0 -16
  111. package/src/components/base/nav/nav_item_dropdown.spec.js +0 -58
  112. package/src/components/base/navbar/navbar.spec.js +0 -21
  113. package/src/components/base/navbar/navbar.stories.js +0 -28
  114. package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.spec.js +0 -566
  115. package/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.spec.js +0 -452
  116. package/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.stories.js +0 -345
  117. package/src/components/base/new_dropdowns/disclosure/disclosure_dropdown_group.spec.js +0 -84
  118. package/src/components/base/new_dropdowns/disclosure/disclosure_dropdown_item.spec.js +0 -200
  119. package/src/components/base/new_dropdowns/disclosure/utils.spec.js +0 -74
  120. package/src/components/base/new_dropdowns/listbox/listbox.spec.js +0 -950
  121. package/src/components/base/new_dropdowns/listbox/listbox.stories.js +0 -840
  122. package/src/components/base/new_dropdowns/listbox/listbox_group.spec.js +0 -61
  123. package/src/components/base/new_dropdowns/listbox/listbox_item.spec.js +0 -129
  124. package/src/components/base/new_dropdowns/listbox/listbox_search_input.spec.js +0 -62
  125. package/src/components/base/new_dropdowns/listbox/utils.spec.js +0 -58
  126. package/src/components/base/paginated_list/__snapshots__/paginated_list.spec.js.snap +0 -922
  127. package/src/components/base/paginated_list/paginated_list.spec.js +0 -212
  128. package/src/components/base/paginated_list/paginated_list.stories.js +0 -207
  129. package/src/components/base/pagination/pagination.spec.js +0 -401
  130. package/src/components/base/pagination/pagination.stories.js +0 -129
  131. package/src/components/base/path/__snapshots__/path.spec.js.snap +0 -586
  132. package/src/components/base/path/path.spec.js +0 -237
  133. package/src/components/base/path/path.stories.js +0 -84
  134. package/src/components/base/popover/popover.spec.js +0 -104
  135. package/src/components/base/popover/popover.stories.js +0 -109
  136. package/src/components/base/progress_bar/progress_bar.stories.js +0 -34
  137. package/src/components/base/search_box_by_click/search_box_by_click.spec.js +0 -209
  138. package/src/components/base/search_box_by_click/search_box_by_click.stories.js +0 -100
  139. package/src/components/base/search_box_by_type/search_box_by_type.spec.js +0 -164
  140. package/src/components/base/search_box_by_type/search_box_by_type.stories.js +0 -63
  141. package/src/components/base/segmented_control/segmented_control.spec.js +0 -135
  142. package/src/components/base/segmented_control/segmented_control.stories.js +0 -59
  143. package/src/components/base/skeleton_loader/skeleton_loader.spec.js +0 -151
  144. package/src/components/base/skeleton_loader/skeleton_loader.stories.js +0 -86
  145. package/src/components/base/sorting/sorting.spec.js +0 -170
  146. package/src/components/base/sorting/sorting.stories.js +0 -98
  147. package/src/components/base/table/table.spec.js +0 -192
  148. package/src/components/base/table/table.stories.js +0 -186
  149. package/src/components/base/table_lite/table_lite.spec.js +0 -40
  150. package/src/components/base/table_lite/table_lite.stories.js +0 -74
  151. package/src/components/base/tabs/tab/tab.spec.js +0 -34
  152. package/src/components/base/tabs/tabs/scrollable_tabs.spec.js +0 -260
  153. package/src/components/base/tabs/tabs/tabs.spec.js +0 -366
  154. package/src/components/base/tabs/tabs/tabs.stories.js +0 -209
  155. package/src/components/base/toast/toast.spec.js +0 -72
  156. package/src/components/base/toast/toast.stories.js +0 -88
  157. package/src/components/base/toggle/toggle.spec.js +0 -168
  158. package/src/components/base/toggle/toggle.stories.js +0 -106
  159. package/src/components/base/token/token.spec.js +0 -48
  160. package/src/components/base/token/token.stories.js +0 -58
  161. package/src/components/base/token_selector/helpers.spec.js +0 -40
  162. package/src/components/base/token_selector/token_container.spec.js +0 -306
  163. package/src/components/base/token_selector/token_selector.spec.js +0 -653
  164. package/src/components/base/token_selector/token_selector.stories.js +0 -114
  165. package/src/components/base/token_selector/token_selector_dropdown.spec.js +0 -347
  166. package/src/components/base/tooltip/tooltip.spec.js +0 -27
  167. package/src/components/base/tooltip/tooltip.stories.js +0 -70
  168. package/src/components/charts/area/area.spec.js +0 -286
  169. package/src/components/charts/area/area.stories.js +0 -208
  170. package/src/components/charts/bar/__snapshots__/bar.spec.js.snap +0 -82
  171. package/src/components/charts/bar/bar.spec.js +0 -92
  172. package/src/components/charts/bar/bar.stories.js +0 -72
  173. package/src/components/charts/chart/chart.spec.js +0 -167
  174. package/src/components/charts/chart/chart.stories.js +0 -111
  175. package/src/components/charts/column/__snapshots__/column_chart.spec.js.snap +0 -323
  176. package/src/components/charts/column/column.stories.js +0 -111
  177. package/src/components/charts/column/column_chart.spec.js +0 -223
  178. package/src/components/charts/discrete_scatter/discrete_scatter.spec.js +0 -173
  179. package/src/components/charts/discrete_scatter/discrete_scatter.stories.js +0 -64
  180. package/src/components/charts/gauge/gauge.spec.js +0 -280
  181. package/src/components/charts/gauge/gauge.stories.js +0 -97
  182. package/src/components/charts/heatmap/heatmap.spec.js +0 -103
  183. package/src/components/charts/heatmap/heatmap.stories.js +0 -78
  184. package/src/components/charts/legend/legend.spec.js +0 -274
  185. package/src/components/charts/legend/legend.stories.js +0 -174
  186. package/src/components/charts/line/line.spec.js +0 -278
  187. package/src/components/charts/line/line.stories.js +0 -238
  188. package/src/components/charts/series_label/series_label.stories.js +0 -78
  189. package/src/components/charts/single_stat/single_stat.spec.js +0 -217
  190. package/src/components/charts/single_stat/single_stat.stories.js +0 -103
  191. package/src/components/charts/sparkline/sparkline.spec.js +0 -220
  192. package/src/components/charts/sparkline/sparkline.stories.js +0 -85
  193. package/src/components/charts/stacked_column/__snapshots__/stacked_column.spec.js.snap +0 -905
  194. package/src/components/charts/stacked_column/stacked_column.spec.js +0 -194
  195. package/src/components/charts/stacked_column/stacked_column.stories.js +0 -130
  196. package/src/components/charts/tooltip/tooltip.spec.js +0 -282
  197. package/src/components/charts/tooltip/tooltip.stories.js +0 -90
  198. package/src/components/experimental/duo/chat/components/duo_chat_conversation/duo_chat_conversation.spec.js +0 -46
  199. package/src/components/experimental/duo/chat/components/duo_chat_conversation/duo_chat_conversation.stories.js +0 -62
  200. package/src/components/experimental/duo/chat/components/duo_chat_loader/duo_chat_loader.spec.js +0 -70
  201. package/src/components/experimental/duo/chat/components/duo_chat_loader/duo_chat_loader.stories.js +0 -31
  202. package/src/components/experimental/duo/chat/components/duo_chat_message/copy_code_element.spec.js +0 -64
  203. package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.spec.js +0 -534
  204. package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.stories.js +0 -55
  205. package/src/components/experimental/duo/chat/components/duo_chat_message/utils.spec.js +0 -24
  206. package/src/components/experimental/duo/chat/components/duo_chat_message_sources/duo_chat_message_sources.spec.js +0 -93
  207. package/src/components/experimental/duo/chat/components/duo_chat_message_sources/duo_chat_message_sources.stories.js +0 -32
  208. package/src/components/experimental/duo/chat/components/duo_chat_predefined_prompts/duo_chat_predefined_prompts.spec.js +0 -35
  209. package/src/components/experimental/duo/chat/components/duo_chat_predefined_prompts/duo_chat_predefined_prompts.stories.js +0 -36
  210. package/src/components/experimental/duo/chat/duo_chat.spec.js +0 -828
  211. package/src/components/experimental/duo/chat/duo_chat.stories.js +0 -240
  212. package/src/components/experimental/duo/chat/markdown_renderer.spec.js +0 -55
  213. package/src/components/experimental/duo/user_feedback/user_feedback.spec.js +0 -100
  214. package/src/components/experimental/duo/user_feedback/user_feedback.stories.js +0 -107
  215. package/src/components/experimental/duo/user_feedback/user_feedback_modal.spec.js +0 -134
  216. package/src/components/experimental/experiment_badge/experiment_badge.spec.js +0 -77
  217. package/src/components/experimental/experiment_badge/experiment_badge.stories.js +0 -58
  218. package/src/components/regions/dashboard_skeleton/dashboard_skeleton.stories.js +0 -29
  219. package/src/components/regions/empty_state/empty_state.spec.js +0 -293
  220. package/src/components/regions/empty_state/empty_state.stories.js +0 -160
  221. package/src/components/shared_components/charts/tooltip_default_format.spec.js +0 -69
  222. package/src/components/shared_components/clear_icon_button/__snapshots__/clear_icon_button.spec.js.snap +0 -15
  223. package/src/components/shared_components/clear_icon_button/clear_icon_button.spec.js +0 -23
  224. package/src/components/shared_components/close_button/__snapshots__/close_button.spec.js.snap +0 -12
  225. package/src/components/shared_components/close_button/close_button.spec.js +0 -22
  226. package/src/components/utilities/animated_number/animated_number.spec.js +0 -133
  227. package/src/components/utilities/animated_number/animated_number.stories.js +0 -63
  228. package/src/components/utilities/friendly_wrap/friendly_wrap.spec.js +0 -123
  229. package/src/components/utilities/friendly_wrap/friendly_wrap.stories.js +0 -56
  230. package/src/components/utilities/intersection_observer/intersection_observer.spec.js +0 -116
  231. package/src/components/utilities/intersection_observer/intersection_observer.stories.js +0 -162
  232. package/src/components/utilities/intersperse/intersperse.spec.js +0 -115
  233. package/src/components/utilities/intersperse/intersperse.stories.js +0 -53
  234. package/src/components/utilities/sprintf/sprintf.spec.js +0 -337
  235. package/src/components/utilities/sprintf/sprintf.stories.js +0 -104
  236. package/src/components/utilities/truncate/truncate.spec.js +0 -125
  237. package/src/components/utilities/truncate/truncate.stories.js +0 -50
  238. package/src/components/utilities/truncate_text/truncate_text.spec.js +0 -150
  239. package/src/components/utilities/truncate_text/truncate_text.stories.js +0 -45
  240. package/src/directives/hover_load/hover_load.spec.js +0 -64
  241. package/src/directives/hover_load/hover_load.stories.js +0 -51
  242. package/src/directives/outside/outside.spec.js +0 -338
  243. package/src/directives/outside/outside.stories.js +0 -35
  244. package/src/directives/resize_observer/resize_observer.spec.js +0 -125
  245. package/src/directives/resize_observer/resize_observer.stories.js +0 -76
  246. package/src/directives/safe_html/safe_html.spec.js +0 -149
  247. package/src/directives/safe_html/safe_html.stories.js +0 -60
  248. package/src/directives/safe_link/safe_link.spec.js +0 -135
  249. package/src/directives/safe_link/safe_link.stories.js +0 -39
  250. package/src/internal/color_contrast/color_contrast.spec.js +0 -34
  251. package/src/internal/color_contrast/color_contrast.stories.js +0 -41
  252. package/src/scss/functions.spec.scss +0 -54
  253. package/src/scss/mixins.spec.scss +0 -93
  254. package/src/scss/run_scss_tests.spec.js +0 -26
  255. package/src/scss/typescale/typescale.stories.js +0 -61
  256. package/src/tokens/color.constant.tokens.stories.js +0 -19
  257. package/src/tokens/color.dark.tokens.stories.js +0 -26
  258. package/src/tokens/color.data_viz.dark.tokens.stories.js +0 -19
  259. package/src/tokens/color.data_viz.tokens.stories.js +0 -19
  260. package/src/tokens/color.theme.dark.tokens.stories.js +0 -21
  261. package/src/tokens/color.theme.tokens.stories.js +0 -21
  262. package/src/tokens/color.tokens.stories.js +0 -26
  263. package/src/tokens/color.transparency.tokens.stories.js +0 -14
  264. package/src/tokens/text.dark.tokens.stories.js +0 -18
  265. package/src/tokens/text.tokens.stories.js +0 -17
  266. package/src/tokens/tokens.stories.js +0 -16
  267. package/src/utils/breakpoints.spec.js +0 -26
  268. package/src/utils/charts/config.spec.js +0 -478
  269. package/src/utils/charts/utils.spec.js +0 -106
  270. package/src/utils/datetime_utility.spec.js +0 -98
  271. package/src/utils/is_slot_empty.spec.js +0 -73
  272. package/src/utils/number_utils.spec.js +0 -110
  273. package/src/utils/stories_utils.spec.js +0 -18
  274. package/src/utils/string_utils.spec.js +0 -56
  275. package/src/utils/utils.spec.js +0 -156
@@ -1,531 +0,0 @@
1
- import cloneDeep from 'lodash/cloneDeep';
2
- import mapValues from 'lodash/mapValues';
3
- import { nextTick } from 'vue';
4
- import { shallowMount, mount } from '@vue/test-utils';
5
- import GlFormGroup from '../form_group/form_group.vue';
6
- import GlInput from '../form_input/form_input.vue';
7
- import GlFormFields from './form_fields.vue';
8
- import GlFormFieldValidator from './form_field_validator.vue';
9
- import * as formMappers from './mappers';
10
- import * as formValidators from './validators';
11
-
12
- jest.mock('lodash/uniqueId', () => (val) => `${val}testunique`);
13
-
14
- const TEST_FIELDS = {
15
- username: {
16
- label: 'User name',
17
- validators: [formValidators.required('User name is required')],
18
- },
19
- evenCount: {
20
- label: 'Count',
21
- mapValue: formMappers.mapToNumber,
22
- validators: [
23
- formValidators.factory('Count is required', Boolean),
24
- (val) => (val % 2 === 1 ? 'Count must be even' : ''),
25
- ],
26
- inputAttrs: { size: 'xs', type: 'number' },
27
- groupAttrs: { class: 'unique-class' },
28
- },
29
- allCaps: {
30
- label: 'All caps (optional)',
31
- mapValue: (x) => x?.toUpperCase(),
32
- },
33
- };
34
- const TEST_VALUES = {
35
- username: 'root',
36
- evenCount: 8,
37
- allCaps: '',
38
- };
39
-
40
- const TEST_FORM_ID = 'test-form-id';
41
-
42
- describe('GlFormFields', () => {
43
- let wrapper;
44
-
45
- // region: factory --------------------------------------------------
46
- const createComponent = (props = {}, options = {}, mountFn = shallowMount) => {
47
- // why: Clone the constant so Vue doesn't turn it reactive
48
- const fields = cloneDeep(TEST_FIELDS);
49
-
50
- wrapper = mountFn(GlFormFields, {
51
- propsData: {
52
- fields,
53
- values: {},
54
- formId: TEST_FORM_ID,
55
- ...props,
56
- },
57
- stubs: {
58
- GlFormFieldValidator,
59
- ...options.stubs,
60
- },
61
- ...options,
62
- });
63
- };
64
-
65
- // region: finders --------------------------------------------------
66
- const mapFormGroupToData = (formGroup) => {
67
- const input = formGroup.findComponent(GlInput);
68
-
69
- return {
70
- label: formGroup.attributes('label'),
71
- state: formGroup.attributes('state'),
72
- invalidFeedback: formGroup.attributes('invalid-feedback'),
73
- class: formGroup.attributes('class'),
74
- input: {
75
- // Ensure that "value" is present even if undefined
76
- value: input.attributes('value'),
77
- ...input.attributes(),
78
- },
79
- };
80
- };
81
- const findFormGroups = () => wrapper.findAllComponents(GlFormGroup).wrappers;
82
- // eslint-disable-next-line unicorn/no-array-callback-reference
83
- const findFormGroupsAsData = () => findFormGroups().map(mapFormGroupToData);
84
- const findFormGroupFromLabel = (label) =>
85
- wrapper.findAllComponents(GlFormGroup).wrappers.find((x) => x.vm.$attrs.label === label);
86
- const findInputFromLabel = (label) => findFormGroupFromLabel(label).findComponent(GlInput);
87
- const findCustomInputFromLabel = (label) =>
88
- findFormGroupFromLabel(label).find('[data-testid="test-custom-input"]');
89
-
90
- // region: actions ---------------------------------------------------
91
- const submitForm = async () => {
92
- const form = document.getElementById(TEST_FORM_ID);
93
-
94
- form.requestSubmit();
95
-
96
- // Submit form waits for a tick
97
- await nextTick();
98
- };
99
-
100
- // region: setup -----------------------------------------------------
101
- beforeEach(() => {
102
- document.body.innerHTML = `<form id="${TEST_FORM_ID}"></form>`;
103
- });
104
-
105
- // region: tests -----------------------------------------------------
106
- describe('default', () => {
107
- beforeEach(() => {
108
- createComponent();
109
- });
110
-
111
- it('renders form groups for each field', () => {
112
- expect(findFormGroupsAsData()).toStrictEqual([
113
- {
114
- label: TEST_FIELDS.username.label,
115
- state: undefined,
116
- invalidFeedback: '',
117
- class: undefined,
118
- input: {
119
- id: 'gl-form-field-testunique',
120
- value: undefined,
121
- },
122
- },
123
- {
124
- label: TEST_FIELDS.evenCount.label,
125
- state: undefined,
126
- invalidFeedback: '',
127
- class: TEST_FIELDS.evenCount.groupAttrs.class,
128
- input: {
129
- id: 'gl-form-field-testunique',
130
- value: '0',
131
- ...TEST_FIELDS.evenCount.inputAttrs,
132
- },
133
- },
134
- {
135
- label: TEST_FIELDS.allCaps.label,
136
- state: undefined,
137
- invalidFeedback: '',
138
- class: undefined,
139
- input: {
140
- id: 'gl-form-field-testunique',
141
- value: undefined,
142
- },
143
- },
144
- ]);
145
- });
146
-
147
- it('emits initial values on mount', () => {
148
- expect(wrapper.emitted('input')).toEqual([
149
- [
150
- {
151
- username: undefined,
152
- evenCount: 0,
153
- allCaps: undefined,
154
- },
155
- ],
156
- ]);
157
- });
158
-
159
- it('does not emit input-field', () => {
160
- expect(wrapper.emitted('input-field')).toBeUndefined();
161
- });
162
-
163
- it('does not emit submit', () => {
164
- expect(wrapper.emitted('submit')).toBeUndefined();
165
- });
166
-
167
- it('on field blur, it updates validation for field', async () => {
168
- const input = findInputFromLabel(TEST_FIELDS.username.label);
169
- input.vm.$emit('blur');
170
-
171
- await nextTick();
172
-
173
- expect(findFormGroupsAsData()).toMatchObject([
174
- {
175
- label: TEST_FIELDS.username.label,
176
- invalidFeedback: 'User name is required',
177
- },
178
- // why: Include others fields to assert that the validation is not run for them
179
- {
180
- label: TEST_FIELDS.evenCount.label,
181
- invalidFeedback: '',
182
- },
183
- {
184
- label: TEST_FIELDS.allCaps.label,
185
- invalidFeedback: '',
186
- },
187
- ]);
188
- });
189
-
190
- describe('on field input', () => {
191
- beforeEach(() => {
192
- const input = findInputFromLabel(TEST_FIELDS.username.label);
193
- input.vm.$emit('input', 'New value');
194
- });
195
-
196
- it('emits input event', () => {
197
- expect(wrapper.emitted('input')).toEqual([
198
- // Initial value we already test here
199
- expect.anything(),
200
- // New emitted event
201
- [
202
- {
203
- username: 'New value',
204
- },
205
- ],
206
- ]);
207
- });
208
-
209
- it('emits input-field event', () => {
210
- expect(wrapper.emitted('input-field')).toEqual([
211
- [
212
- {
213
- name: 'username',
214
- value: 'New value',
215
- },
216
- ],
217
- ]);
218
- });
219
- });
220
-
221
- describe('when form submits', () => {
222
- beforeEach(async () => {
223
- await submitForm();
224
- });
225
-
226
- it('runs validations', () => {
227
- expect(findFormGroupsAsData()).toEqual([
228
- {
229
- label: TEST_FIELDS.username.label,
230
- invalidFeedback: 'User name is required',
231
- state: undefined,
232
- class: undefined,
233
- input: {
234
- value: undefined,
235
- id: 'gl-form-field-testunique',
236
- },
237
- },
238
- {
239
- label: TEST_FIELDS.evenCount.label,
240
- invalidFeedback: 'Count is required',
241
- state: undefined,
242
- class: TEST_FIELDS.evenCount.groupAttrs.class,
243
- input: expect.objectContaining({
244
- value: '0',
245
- id: 'gl-form-field-testunique',
246
- }),
247
- },
248
- {
249
- label: TEST_FIELDS.allCaps.label,
250
- invalidFeedback: '',
251
- state: undefined,
252
- class: undefined,
253
- input: {
254
- value: undefined,
255
- id: 'gl-form-field-testunique',
256
- },
257
- },
258
- ]);
259
- });
260
-
261
- it('does not emit submit', () => {
262
- expect(wrapper.emitted('submit')).toBeUndefined();
263
- });
264
- });
265
- });
266
-
267
- // why: Let's test that multiple validators work as expected
268
- describe('with non-empty but invalid "count"', () => {
269
- beforeEach(() => {
270
- createComponent({
271
- values: {
272
- evenCount: 7,
273
- },
274
- });
275
- });
276
-
277
- it('on blur, it runs remaining validators for "count" field', async () => {
278
- const input = findInputFromLabel(TEST_FIELDS.evenCount.label);
279
- input.vm.$emit('blur');
280
-
281
- await nextTick();
282
-
283
- expect(findFormGroupsAsData().find((x) => x.label === TEST_FIELDS.evenCount.label)).toEqual({
284
- label: TEST_FIELDS.evenCount.label,
285
- invalidFeedback: 'Count must be even',
286
- state: undefined,
287
- class: TEST_FIELDS.evenCount.groupAttrs.class,
288
- input: {
289
- ...TEST_FIELDS.evenCount.inputAttrs,
290
- id: 'gl-form-field-testunique',
291
- value: '7',
292
- },
293
- });
294
- });
295
- });
296
-
297
- describe('with valid values', () => {
298
- beforeEach(() => {
299
- const values = cloneDeep(TEST_VALUES);
300
-
301
- createComponent({
302
- values,
303
- });
304
- });
305
-
306
- it('when form submits, emits submit', async () => {
307
- await submitForm();
308
-
309
- expect(wrapper.emitted('submit')).toEqual([[expect.any(Event)]]);
310
- });
311
-
312
- describe.each`
313
- fieldName | inputValue | expectedValue
314
- ${'allCaps'} | ${'foo bar'} | ${'FOO BAR'}
315
- ${'evenCount'} | ${'123'} | ${123}
316
- `(
317
- 'when input ("$fieldName") with mapValue changes',
318
- ({ fieldName, inputValue, expectedValue }) => {
319
- beforeEach(() => {
320
- const input = findInputFromLabel(TEST_FIELDS[fieldName].label);
321
- input.vm.$emit('input', inputValue);
322
- });
323
-
324
- it('emits input with mapped value', () => {
325
- expect(wrapper.emitted('input')).toEqual([
326
- // Ignore initial inputted value (already tested)
327
- expect.anything(),
328
- [
329
- {
330
- ...TEST_VALUES,
331
- [fieldName]: expectedValue,
332
- },
333
- ],
334
- ]);
335
- });
336
-
337
- it('emits input-field with mapped value', () => {
338
- expect(wrapper.emitted('input-field')).toEqual([
339
- [
340
- {
341
- name: fieldName,
342
- value: expectedValue,
343
- },
344
- ],
345
- ]);
346
- });
347
- }
348
- );
349
-
350
- describe('when there is a server validation message', () => {
351
- beforeEach(async () => {
352
- await submitForm();
353
-
354
- wrapper.setProps({
355
- serverValidations: { username: 'Username has already been taken.' },
356
- });
357
- });
358
-
359
- it('renders error message', () => {
360
- expect(
361
- findFormGroupFromLabel(TEST_FIELDS.username.label).attributes('invalid-feedback')
362
- ).toBe('Username has already been taken.');
363
- });
364
- });
365
- });
366
-
367
- describe('with input scoped slot', () => {
368
- beforeEach(() => {
369
- createComponent(
370
- {
371
- values: {
372
- evenCount: 5,
373
- },
374
- },
375
- {
376
- scopedSlots: {
377
- 'input(evenCount)':
378
- '<button data-testid="test-custom-input" @click="props.input(props.value + 1)" @blur="props.blur">{{ props.value }}</button>',
379
- },
380
- }
381
- );
382
- });
383
-
384
- it('renders scoped slot for field input', () => {
385
- expect(findInputFromLabel(TEST_FIELDS.evenCount.label).exists()).toBe(false);
386
- expect(findCustomInputFromLabel(TEST_FIELDS.evenCount.label).exists()).toBe(true);
387
- });
388
-
389
- it('passes down "blur" callback', async () => {
390
- // what: We'll test this by emitting the "blur" we attached in the scopedSlot
391
- // and asserting that validation was ran.
392
- expect(
393
- findFormGroupFromLabel(TEST_FIELDS.evenCount.label).attributes('invalid-feedback')
394
- ).toBe('');
395
-
396
- findCustomInputFromLabel(TEST_FIELDS.evenCount.label).trigger('blur');
397
- await nextTick();
398
-
399
- expect(
400
- findFormGroupFromLabel(TEST_FIELDS.evenCount.label).attributes('invalid-feedback')
401
- ).toBe('Count must be even');
402
- });
403
-
404
- it('passes down "input" callback', () => {
405
- // what: We'll test this by checking that the input-field event
406
- // is emitted when triggered by our scoped slot.
407
- expect(wrapper.emitted('input-field')).toBeUndefined();
408
-
409
- findCustomInputFromLabel(TEST_FIELDS.evenCount.label).trigger('click');
410
-
411
- expect(wrapper.emitted('input-field')).toEqual([[{ name: 'evenCount', value: 6 }]]);
412
- });
413
-
414
- it('passes down "value"', () => {
415
- expect(findCustomInputFromLabel(TEST_FIELDS.evenCount.label).text()).toEqual('5');
416
- });
417
- });
418
-
419
- describe('with form group scoped slots', () => {
420
- it('renders description slot', () => {
421
- createComponent(
422
- {},
423
- {
424
- scopedSlots: {
425
- 'group(username)-description': '<div data-testid="group-description-slot"></div>',
426
- },
427
- },
428
- mount
429
- );
430
-
431
- expect(
432
- findFormGroupFromLabel(TEST_FIELDS.username.label)
433
- .find('[data-testid="group-description-slot"]')
434
- .exists()
435
- ).toBe(true);
436
- });
437
-
438
- it('renders label slot', () => {
439
- createComponent(
440
- {},
441
- {
442
- scopedSlots: {
443
- 'group(username)-label': `<div data-testid="group-label-slot">${TEST_FIELDS.username.label}</div>`,
444
- },
445
- },
446
- mount
447
- );
448
-
449
- expect(
450
- wrapper
451
- .findAllComponents(GlFormGroup)
452
- .wrappers.find((x) => x.text().includes(TEST_FIELDS.username.label))
453
- .find('[data-testid="group-label-slot"]')
454
- .exists()
455
- ).toBe(true);
456
- });
457
-
458
- it('renders label description slot', () => {
459
- createComponent(
460
- {},
461
- {
462
- scopedSlots: {
463
- 'group(username)-label-description':
464
- '<div data-testid="group-label-description-slot"></div>',
465
- },
466
- },
467
- mount
468
- );
469
-
470
- expect(
471
- findFormGroupFromLabel(TEST_FIELDS.username.label)
472
- .find('[data-testid="group-label-description-slot"]')
473
- .exists()
474
- ).toBe(true);
475
- });
476
- });
477
-
478
- describe('with after slot', () => {
479
- beforeEach(() => {
480
- createComponent(
481
- {},
482
- {
483
- scopedSlots: {
484
- 'after(username)': '<div data-testid="after-slot"></div>',
485
- },
486
- }
487
- );
488
- });
489
-
490
- it('renders after slot', () => {
491
- expect(
492
- findFormGroupFromLabel(TEST_FIELDS.username.label).element.nextElementSibling.getAttribute(
493
- 'data-testid'
494
- )
495
- ).toBe('after-slot');
496
- });
497
- });
498
-
499
- // why: We have to do some manual reactivity to optimize how often we call
500
- // field validators. Let's test that here.
501
- describe('validation performance', () => {
502
- let validationSpy;
503
-
504
- beforeEach(async () => {
505
- validationSpy = jest.fn().mockReturnValue('Not valid!');
506
-
507
- createComponent({
508
- fields: mapValues(TEST_FIELDS, (field) => ({
509
- ...field,
510
- validators: [validationSpy],
511
- })),
512
- });
513
-
514
- // Trigger validation on all fields so that the fields are "dirty"
515
- await submitForm();
516
-
517
- // Clear validationSpy so we can assert on *new* validation calls
518
- validationSpy.mockClear();
519
- });
520
-
521
- it('when input changes, only triggers validation for changed value', async () => {
522
- expect(validationSpy).not.toHaveBeenCalled();
523
-
524
- wrapper.setProps({ values: { username: 'root' } });
525
- await nextTick();
526
-
527
- expect(validationSpy).toHaveBeenCalledTimes(1);
528
- expect(validationSpy).toHaveBeenCalledWith('root');
529
- });
530
- });
531
- });
@@ -1,150 +0,0 @@
1
- import uniqueId from 'lodash/uniqueId';
2
- import omit from 'lodash/omit';
3
- import GlModal from '../../modal/modal.vue';
4
- import GlButton from '../../button/button.vue';
5
- import GlAlert from '../../alert/alert.vue';
6
- import GlListbox from '../../new_dropdowns/listbox/listbox.vue';
7
- import GlIcon from '../../icon/icon.vue';
8
- import { setStoryTimeout } from '../../../../utils/test_utils';
9
- import GlFormFields from './form_fields.vue';
10
- import readme from './form_fields.md';
11
- import { required } from './validators';
12
- import { mapToNumber } from './mappers';
13
-
14
- const Template = () => ({
15
- ITEMS: ['Pizza', 'Keyboards', 'Guitars', 'Rocket ships'].map((text) => ({ text, value: text })),
16
- components: { GlFormFields, GlButton, GlModal, GlListbox, GlAlert, GlIcon },
17
- data() {
18
- return {
19
- // why: We declare fields here so that we can test what binding the
20
- // "confirmPassword" validator to "this.formValues" would act
21
- // like. In most cases, these can be constant and injected through
22
- // `$options`.
23
- fields: {
24
- USERNAME: {
25
- label: 'NAME (ALL CAPS)',
26
- mapValue: (x) => x?.toUpperCase(),
27
- validators: [required('NAME IS REQUIRED!!!')],
28
- },
29
- password: {
30
- label: 'Password with group styling',
31
- inputAttrs: { type: 'password' },
32
- groupAttrs: { class: 'gl-bg-purple-50 gl-w-20' },
33
- validators: [required('Password is required')],
34
- },
35
- confirmPassword: {
36
- label: 'Confirm Password',
37
- inputAttrs: { type: 'password' },
38
- validators: [
39
- required('Confirmed password is required'),
40
- (confirmValue) =>
41
- confirmValue !== this.formValues.password ? 'Must match password' : '',
42
- ],
43
- },
44
- custom: {
45
- label: 'Custom input',
46
- mapValue: mapToNumber,
47
- validators: [(val) => (val < 1 ? 'Please click this at least once :)' : '')],
48
- },
49
- favoriteItem: {
50
- label: 'Favorite Item (Optional)',
51
- },
52
- },
53
- formValues: {},
54
- testFormId: uniqueId('form_fields_story_'),
55
- serverValidations: {},
56
- loading: false,
57
- };
58
- },
59
- computed: {
60
- values() {
61
- return omit(this.formValues, ['confirmPassword']);
62
- },
63
- valuesJSON() {
64
- // JSON doesn't allow undefined values
65
- return JSON.stringify(this.values, (key, value) => (value === undefined ? null : value), 2);
66
- },
67
- favoriteItemToggleText() {
68
- if (!this.formValues.favoriteItem) {
69
- return 'Select an item';
70
- }
71
-
72
- return null;
73
- },
74
- },
75
- methods: {
76
- onInputField({ name }) {
77
- this.$delete(this.serverValidations, name);
78
- },
79
- async onSubmit() {
80
- this.loading = true;
81
-
82
- // Simulate waiting for API request to resolve
83
- await new Promise((resolve) => {
84
- setStoryTimeout(resolve, 1000);
85
- });
86
-
87
- this.loading = false;
88
-
89
- // Manually checking field and validating for this example.
90
- // In practice this error message would come from the API response.
91
- if (this.formValues.USERNAME === 'FOO') {
92
- this.$set(this.serverValidations, 'USERNAME', 'Username has already been taken.');
93
-
94
- return;
95
- }
96
-
97
- this.$refs.modal.show();
98
- },
99
- },
100
- template: `
101
- <div>
102
- <h3>Fields</h3>
103
- <form :id="testFormId" @submit.prevent>
104
- <gl-form-fields :fields="fields" v-model="formValues" :form-id="testFormId" :server-validations="serverValidations" @input-field="onInputField" @submit="onSubmit">
105
- <template #group(confirmPassword)-label>
106
- <div class="gl-display-flex gl-align-items-center gl-column-gap-3">
107
- <span>Confirm Password</span>
108
- <gl-icon name="information-o" />
109
- </div>
110
- </template>
111
- <template #group(confirmPassword)-description>
112
- Description using <code>group(confirmPassword)-description</code> slot
113
- </template>
114
- <template #after(confirmPassword)>
115
- <gl-alert class="gl-mb-5" :dismissible="false">Custom content using <code>after(confirmPassword)</code> slot</gl-alert>
116
- </template>
117
- <template #input(custom)="{ id, value, input, blur }">
118
- <button :id="id" @click="input(value + 1)" @blur="blur" type="button">{{value}}</button>
119
- </template>
120
- <template #input(favoriteItem)="{ id, value, input, blur }">
121
- <gl-listbox :id="id" :items="$options.ITEMS" :selected="value" :toggle-text="favoriteItemToggleText" @select="input" @hidden="blur" />
122
- </template>
123
- <template #group(favoriteItem)-label-description>
124
- Label description using <code>group(favoriteItem)-label-description</code> slot
125
- </template>
126
- </gl-form-fields>
127
- <gl-button type="submit" category="primary" :loading="loading">Submit</gl-button>
128
- </form>
129
- <gl-modal ref="modal" modal-id="submission-modal" title="Form submission"><pre>{{ valuesJSON }}</pre></gl-modal>
130
- </div>
131
- `,
132
- });
133
-
134
- export const Default = Template.bind({});
135
-
136
- export default {
137
- title: 'base/form/form-fields',
138
- component: GlFormFields,
139
- parameters: {
140
- knobs: {
141
- disable: true,
142
- },
143
- docs: {
144
- description: {
145
- component: readme,
146
- },
147
- },
148
- },
149
- argTypes: {},
150
- };
@@ -1,17 +0,0 @@
1
- import { mapToNumber } from './mappers';
2
-
3
- describe('components/base/form/form_fields/mappers', () => {
4
- describe('mapToNumber', () => {
5
- it.each`
6
- input | output
7
- ${''} | ${0}
8
- ${false} | ${0}
9
- ${{}} | ${Number.NaN}
10
- ${'888'} | ${888}
11
- ${'-5e10'} | ${-50000000000}
12
- ${'55.78'} | ${55.78}
13
- `('with $input, returns $output', ({ input, output }) => {
14
- expect(mapToNumber(input)).toBe(output);
15
- });
16
- });
17
- });