@gitlab/ui 72.7.0 → 72.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/components/base/dropdown/dropdown.js +2 -6
- package/dist/components/base/filtered_search/filtered_search_term.js +2 -0
- package/dist/components/base/filtered_search/filtered_search_token.js +3 -1
- package/dist/components/base/filtered_search/filtered_search_token_segment.js +4 -1
- package/dist/components/base/form/form_checkbox_tree/checkbox_tree_node.js +1 -1
- package/dist/components/base/new_dropdowns/base_dropdown/base_dropdown.js +9 -0
- package/dist/components/base/new_dropdowns/disclosure/utils.js +5 -1
- package/dist/components/base/new_dropdowns/listbox/utils.js +6 -0
- package/dist/components/base/sorting/sorting.js +1 -0
- package/dist/components/base/token_selector/token_selector.js +1 -1
- package/dist/components/charts/column/column.js +1 -1
- package/dist/components/experimental/duo/chat/components/duo_chat_conversation/duo_chat_conversation.js +2 -0
- package/dist/components/experimental/duo/chat/duo_chat.js +2 -0
- package/dist/components/utilities/sprintf/sprintf.js +3 -1
- package/dist/tokens/css/tokens.css +1 -1
- package/dist/tokens/css/tokens.dark.css +1 -1
- package/dist/tokens/js/tokens.dark.js +1 -1
- package/dist/tokens/js/tokens.js +1 -1
- package/dist/tokens/scss/_tokens.dark.scss +1 -1
- package/dist/tokens/scss/_tokens.scss +1 -1
- package/dist/utils/is_slot_empty.js +4 -0
- package/dist/utils/number_utils.js +1 -1
- package/dist/utils/utils.js +1 -1
- package/package.json +3 -3
- package/src/components/base/broadcast_message/broadcast_message.stories.js +1 -0
- package/src/components/base/drawer/drawer.spec.js +1 -0
- package/src/components/base/dropdown/dropdown.vue +2 -6
- package/src/components/base/filtered_search/filtered_search.spec.js +13 -2
- package/src/components/base/filtered_search/filtered_search_term.spec.js +2 -4
- package/src/components/base/filtered_search/filtered_search_term.vue +1 -0
- package/src/components/base/filtered_search/filtered_search_token.vue +1 -0
- package/src/components/base/filtered_search/filtered_search_token_segment.spec.js +5 -7
- package/src/components/base/filtered_search/filtered_search_token_segment.vue +3 -1
- package/src/components/base/form/form_checkbox_tree/checkbox_tree_node.vue +1 -1
- package/src/components/base/form/form_checkbox_tree/form_checkbox_tree.spec.js +2 -2
- package/src/components/base/form/form_fields/form_fields.spec.js +1 -0
- package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.spec.js +41 -4
- package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.vue +12 -0
- package/src/components/base/new_dropdowns/disclosure/utils.js +2 -0
- package/src/components/base/new_dropdowns/listbox/listbox.spec.js +2 -0
- package/src/components/base/new_dropdowns/listbox/listbox.stories.js +4 -0
- package/src/components/base/new_dropdowns/listbox/utils.js +3 -0
- package/src/components/base/search_box_by_click/search_box_by_click.spec.js +2 -4
- package/src/components/base/sorting/sorting.vue +1 -0
- package/src/components/base/token_selector/token_selector.spec.js +2 -2
- package/src/components/base/token_selector/token_selector.vue +1 -1
- package/src/components/charts/column/__snapshots__/column_chart.spec.js.snap +1 -1
- package/src/components/charts/column/column.stories.js +18 -3
- package/src/components/charts/column/column.vue +16 -1
- package/src/components/charts/column/column_chart.spec.js +106 -20
- package/src/components/experimental/duo/chat/components/duo_chat_conversation/duo_chat_conversation.vue +1 -0
- package/src/components/experimental/duo/chat/duo_chat.vue +1 -0
- package/src/components/utilities/intersection_observer/intersection_observer.spec.js +1 -0
- package/src/components/utilities/intersperse/intersperse.spec.js +1 -0
- package/src/components/utilities/sprintf/sprintf.vue +1 -0
- package/src/utils/is_slot_empty.js +3 -0
- package/src/utils/number_utils.js +1 -1
- package/src/utils/utils.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [72.8.1](https://gitlab.com/gitlab-org/gitlab-ui/compare/v72.8.0...v72.8.1) (2024-01-18)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **dropdowns:** do not steal focus back from the consumer ([69295fa](https://gitlab.com/gitlab-org/gitlab-ui/commit/69295faa28dfe48b90e75ab86c9d3ba615760c96))
|
|
7
|
+
|
|
8
|
+
# [72.8.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v72.7.0...v72.8.0) (2024-01-16)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **GlColumnChart:** allow customising chart tooltip ([ed6ee9e](https://gitlab.com/gitlab-org/gitlab-ui/commit/ed6ee9e3139cba2793d90d955c39c41f6ab2b5af))
|
|
14
|
+
|
|
1
15
|
# [72.7.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v72.6.0...v72.7.0) (2024-01-14)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import Vue from 'vue';
|
|
2
2
|
import { BDropdown } from 'bootstrap-vue/esm/index.js';
|
|
3
|
-
import { selectAll
|
|
3
|
+
import { selectAll } from 'bootstrap-vue/esm/utils/dom';
|
|
4
4
|
import merge from 'lodash/merge';
|
|
5
5
|
import { buttonCategoryOptions, dropdownVariantOptions, buttonSizeOptions } from '../../../utils/constants';
|
|
6
|
+
import { filterVisible } from '../../../utils/utils';
|
|
6
7
|
import { ButtonMixin } from '../../mixins/button_mixin';
|
|
7
8
|
import GlButton from '../button/button';
|
|
8
9
|
import GlIcon from '../icon/icon';
|
|
@@ -11,11 +12,6 @@ import GlDropdownDivider from './dropdown_divider';
|
|
|
11
12
|
import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
|
|
12
13
|
|
|
13
14
|
//
|
|
14
|
-
|
|
15
|
-
// Return an Array of visible items
|
|
16
|
-
function filterVisible(els) {
|
|
17
|
-
return (els || []).filter(isVisible);
|
|
18
|
-
}
|
|
19
15
|
const Selector = {
|
|
20
16
|
ITEM_SELECTOR: '.dropdown-item:not(.disabled):not([disabled]),.form-control:not(.disabled):not([disabled])'
|
|
21
17
|
};
|
|
@@ -109,7 +109,9 @@ var script = {
|
|
|
109
109
|
return hasData || this.isSegmentActive(SEGMENT_DATA);
|
|
110
110
|
},
|
|
111
111
|
availableTokensWithSelf() {
|
|
112
|
-
return [this.config, ...this.availableTokens.filter(token => token !== this.config)].map(
|
|
112
|
+
return [this.config, ...this.availableTokens.filter(token => token !== this.config)].map(
|
|
113
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
114
|
+
tokenToOption);
|
|
113
115
|
},
|
|
114
116
|
operatorDescription() {
|
|
115
117
|
const operator = this.operators.find(op => op.value === this.tokenValue.operator);
|
|
@@ -28,7 +28,9 @@ const isVue3Fragment = vnode => {
|
|
|
28
28
|
const isVNodeEmpty = vnode => {
|
|
29
29
|
if (isVue3Fragment(vnode)) {
|
|
30
30
|
// vnode.children might be an array or single node in edge cases
|
|
31
|
-
return Array.isArray(vnode.children) ?
|
|
31
|
+
return Array.isArray(vnode.children) ?
|
|
32
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
33
|
+
vnode.children.every(isVNodeEmpty) : isVNodeEmpty(vnode.children);
|
|
32
34
|
}
|
|
33
35
|
if (isVue3Comment(vnode)) {
|
|
34
36
|
return true;
|
|
@@ -40,6 +42,7 @@ const isSlotNotEmpty = slot => {
|
|
|
40
42
|
return false;
|
|
41
43
|
}
|
|
42
44
|
const vnodes = typeof slot === 'function' ? slot() : slot;
|
|
45
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
43
46
|
return !(Array.isArray(vnodes) ? vnodes.every(isVNodeEmpty) : isVNodeEmpty(vnodes));
|
|
44
47
|
};
|
|
45
48
|
var script = {
|
|
@@ -44,7 +44,7 @@ var script = {
|
|
|
44
44
|
const __vue_script__ = script;
|
|
45
45
|
|
|
46
46
|
/* template */
|
|
47
|
-
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{class:_vm.rootClass,attrs:{"data-
|
|
47
|
+
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{class:_vm.rootClass,attrs:{"data-testid":("" + (_vm.$options.qaPrefix) + (_vm.option.value))}},[_c('gl-form-checkbox',{class:_vm.checkboxClass,attrs:{"checked":_vm.node.isChecked,"indeterminate":_vm.node.isIndeterminate},on:{"change":function($event){return _vm.tree.toggleOption(_vm.option, $event)}}},[_vm._v("\n "+_vm._s(_vm.label)+"\n ")]),_vm._v(" "),_vm._l((_vm.option.children),function(child){return _c('gl-form-checkbox-tree-node',{key:child.value,attrs:{"option":child,"is-nested":""}})})],2)};
|
|
48
48
|
var __vue_staticRenderFns__ = [];
|
|
49
49
|
|
|
50
50
|
/* style */
|
|
@@ -359,11 +359,20 @@ var script = {
|
|
|
359
359
|
}
|
|
360
360
|
this.toggle(event);
|
|
361
361
|
},
|
|
362
|
+
/**
|
|
363
|
+
* Closes the dropdown and returns the focus to the toggle unless it has has moved outside
|
|
364
|
+
* of the dropdown, meaning that the consumer needed to put some other element in focus.
|
|
365
|
+
*
|
|
366
|
+
* @param {KeyboardEvent?} event The keyboard event that caused the dropdown to close.
|
|
367
|
+
*/
|
|
362
368
|
async closeAndFocus(event) {
|
|
363
369
|
if (!this.visible) {
|
|
364
370
|
return;
|
|
365
371
|
}
|
|
366
372
|
const hasToggled = await this.toggle(event);
|
|
373
|
+
if (!this.$el.contains(document.activeElement)) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
367
376
|
if (hasToggled) {
|
|
368
377
|
this.focusToggle();
|
|
369
378
|
}
|
|
@@ -6,7 +6,11 @@ const itemValidator = item => {
|
|
|
6
6
|
return (item === null || item === void 0 ? void 0 : (_item$text = item.text) === null || _item$text === void 0 ? void 0 : _item$text.length) > 0 && !Array.isArray(item === null || item === void 0 ? void 0 : item.items);
|
|
7
7
|
};
|
|
8
8
|
const isItem = item => Boolean(item) && itemValidator(item);
|
|
9
|
-
const isGroup = group => Boolean(group) && Array.isArray(group.items) && Boolean(group.items.length) &&
|
|
9
|
+
const isGroup = group => Boolean(group) && Array.isArray(group.items) && Boolean(group.items.length) &&
|
|
10
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
11
|
+
group.items.every(isItem);
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
10
14
|
const itemsValidator = items => items.every(isItem) || items.every(isGroup);
|
|
11
15
|
const isListItem = tag => ['gl-disclosure-dropdown-group', 'gl-disclosure-dropdown-item', 'li'].includes(tag);
|
|
12
16
|
const isValidSlotTagVue2 = vNode => {
|
|
@@ -2,6 +2,8 @@ import isNumber from 'lodash/isNumber';
|
|
|
2
2
|
import isString from 'lodash/isString';
|
|
3
3
|
|
|
4
4
|
const isOption = item => Boolean(item) && (isString(item.value) || isNumber(item.value));
|
|
5
|
+
|
|
6
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
5
7
|
const isGroup = function () {
|
|
6
8
|
let {
|
|
7
9
|
options
|
|
@@ -10,6 +12,8 @@ const isGroup = function () {
|
|
|
10
12
|
};
|
|
11
13
|
const hasNoDuplicates = array => array.length === new Set(array).size;
|
|
12
14
|
const flattenedOptions = items => items.flatMap(item => isOption(item) ? item : item.options);
|
|
15
|
+
|
|
16
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
13
17
|
const isAllOptionsOrAllGroups = items => items.every(isOption) || items.every(isGroup);
|
|
14
18
|
const hasUniqueValues = items => hasNoDuplicates(flattenedOptions(items).map(_ref => {
|
|
15
19
|
let {
|
|
@@ -17,6 +21,8 @@ const hasUniqueValues = items => hasNoDuplicates(flattenedOptions(items).map(_re
|
|
|
17
21
|
} = _ref;
|
|
18
22
|
return value;
|
|
19
23
|
}));
|
|
24
|
+
|
|
25
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
20
26
|
const hasUniqueGroups = items => hasNoDuplicates(items.filter(isGroup).map(_ref2 => {
|
|
21
27
|
let {
|
|
22
28
|
text
|
|
@@ -91,7 +91,7 @@ var script = {
|
|
|
91
91
|
default: null
|
|
92
92
|
},
|
|
93
93
|
/**
|
|
94
|
-
* HTML attributes to add to the text input. Helpful for adding `data-testid`
|
|
94
|
+
* HTML attributes to add to the text input. Helpful for adding `data-testid` attributes
|
|
95
95
|
*/
|
|
96
96
|
textInputAttrs: {
|
|
97
97
|
type: Object,
|
|
@@ -191,7 +191,7 @@ const __vue_script__ = script;
|
|
|
191
191
|
/* template */
|
|
192
192
|
var __vue_render__ = function () {
|
|
193
193
|
var _obj;
|
|
194
|
-
var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"position-relative",class:( _obj = {}, _obj[_vm.$options.HEIGHT_AUTO_CLASSES] = _vm.autoHeight, _obj )},[_c('chart',_vm._g(_vm._b({class:{ 'gl-flex-grow-1': _vm.autoHeight },attrs:{"height":_vm.height,"options":_vm.options},on:{"created":_vm.onCreated}},'chart',_vm.$attrs,false),_vm.$listeners)),_vm._v(" "),(_vm.chart)?_c('chart-tooltip',{attrs:{"chart":_vm.chart,"use-default-tooltip-formatter":true}}):_vm._e()],1)};
|
|
194
|
+
var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"position-relative",class:( _obj = {}, _obj[_vm.$options.HEIGHT_AUTO_CLASSES] = _vm.autoHeight, _obj )},[_c('chart',_vm._g(_vm._b({class:{ 'gl-flex-grow-1': _vm.autoHeight },attrs:{"height":_vm.height,"options":_vm.options},on:{"created":_vm.onCreated}},'chart',_vm.$attrs,false),_vm.$listeners)),_vm._v(" "),(_vm.chart)?_c('chart-tooltip',{ref:"dataTooltip",attrs:{"chart":_vm.chart,"use-default-tooltip-formatter":true},scopedSlots:_vm._u([(_vm.$scopedSlots['tooltip-title'])?{key:"title",fn:function(scope){return [_vm._t("tooltip-title",null,null,scope)]}}:null,(_vm.$scopedSlots['tooltip-content'])?{key:"default",fn:function(scope){return [_vm._t("tooltip-content",null,null,scope)]}}:null,(_vm.$scopedSlots['tooltip-value'])?{key:"tooltip-value",fn:function(scope){return [_vm._t("tooltip-value",null,null,scope)]}}:null],null,true)}):_vm._e()],1)};
|
|
195
195
|
var __vue_staticRenderFns__ = [];
|
|
196
196
|
|
|
197
197
|
/* style */
|
|
@@ -5,6 +5,8 @@ const i18n = {
|
|
|
5
5
|
CONVERSATION_NEW_CHAT: 'New chat'
|
|
6
6
|
};
|
|
7
7
|
const isMessage = item => Boolean(item) && (item === null || item === void 0 ? void 0 : item.role);
|
|
8
|
+
|
|
9
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
8
10
|
const itemsValidator = items => items.every(isMessage);
|
|
9
11
|
var script = {
|
|
10
12
|
name: 'GlDuoChatConversation',
|
|
@@ -48,6 +48,8 @@ const slashCommands = [{
|
|
|
48
48
|
description: 'Explain the selected snippet.'
|
|
49
49
|
}];
|
|
50
50
|
const isMessage = item => Boolean(item) && (item === null || item === void 0 ? void 0 : item.role);
|
|
51
|
+
|
|
52
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
51
53
|
const itemsValidator = items => items.every(isMessage);
|
|
52
54
|
var script = {
|
|
53
55
|
name: 'GlDuoChat',
|
|
@@ -63,7 +63,9 @@ var script = {
|
|
|
63
63
|
type: Object,
|
|
64
64
|
required: false,
|
|
65
65
|
default: undefined,
|
|
66
|
-
validator: value => Object.values(value).every(
|
|
66
|
+
validator: value => Object.values(value).every(
|
|
67
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
68
|
+
tagPair => Array.isArray(tagPair) && tagPair.length === 2 && tagPair.every(isString))
|
|
67
69
|
}
|
|
68
70
|
},
|
|
69
71
|
/**
|
package/dist/tokens/js/tokens.js
CHANGED
|
@@ -13,10 +13,12 @@ function isEmpty(vnode) {
|
|
|
13
13
|
return true;
|
|
14
14
|
}
|
|
15
15
|
if (Array.isArray(vnode)) {
|
|
16
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
16
17
|
return vnode.every(isEmpty);
|
|
17
18
|
}
|
|
18
19
|
if (Fragment && vnode.type === Fragment) {
|
|
19
20
|
// Vue.js 3 fragment, check children
|
|
21
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
20
22
|
return vnode.children.every(isEmpty);
|
|
21
23
|
}
|
|
22
24
|
return false;
|
|
@@ -28,6 +30,8 @@ function isSlotEmpty(vueInstance, slot, slotArgs) {
|
|
|
28
30
|
// we need to check both $slots and $scopedSlots due to https://github.com/vuejs/core/issues/8869
|
|
29
31
|
// additionally, in @vue/compat $slot might be a function instead of array of vnodes (sigh)
|
|
30
32
|
callIfNeeded(vueInstance.$slots[slot] || vueInstance.$scopedSlots[slot], slotArgs) : (_vueInstance$$scopedS = (_vueInstance$$scopedS2 = vueInstance.$scopedSlots)[slot]) === null || _vueInstance$$scopedS === void 0 ? void 0 : _vueInstance$$scopedS.call(_vueInstance$$scopedS2, slotArgs);
|
|
33
|
+
|
|
34
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
31
35
|
return isEmpty(slotContent);
|
|
32
36
|
}
|
|
33
37
|
|
package/dist/utils/utils.js
CHANGED
|
@@ -171,7 +171,7 @@ function stopEvent(event) {
|
|
|
171
171
|
* Return an Array of visible items
|
|
172
172
|
*/
|
|
173
173
|
function filterVisible(els) {
|
|
174
|
-
return (els || []).filter(isVisible);
|
|
174
|
+
return (els || []).filter(el => isVisible(el));
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
export { colorFromBackground, debounceByAnimationFrame, filterVisible, focusFirstFocusableElement, hexToRgba, isDev, isElementFocusable, isElementTabbable, logWarning, relativeLuminance, rgbFromHex, rgbFromString, stopEvent, throttle, toSrgb, uid };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitlab/ui",
|
|
3
|
-
"version": "72.
|
|
3
|
+
"version": "72.8.1",
|
|
4
4
|
"description": "GitLab UI Components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -93,10 +93,10 @@
|
|
|
93
93
|
"@babel/preset-env": "^7.23.8",
|
|
94
94
|
"@babel/preset-react": "^7.23.3",
|
|
95
95
|
"@cypress/grep": "^4.0.1",
|
|
96
|
-
"@gitlab/eslint-plugin": "19.
|
|
96
|
+
"@gitlab/eslint-plugin": "19.4.0",
|
|
97
97
|
"@gitlab/fonts": "^1.3.0",
|
|
98
98
|
"@gitlab/stylelint-config": "5.0.1",
|
|
99
|
-
"@gitlab/svgs": "3.
|
|
99
|
+
"@gitlab/svgs": "3.75.0",
|
|
100
100
|
"@rollup/plugin-commonjs": "^11.1.0",
|
|
101
101
|
"@rollup/plugin-node-resolve": "^7.1.3",
|
|
102
102
|
"@rollup/plugin-replace": "^2.3.2",
|
|
@@ -69,6 +69,7 @@ const StackedStory = (args, { argTypes }) => ({
|
|
|
69
69
|
GlBroadcastMessage,
|
|
70
70
|
},
|
|
71
71
|
props: Object.keys(argTypes),
|
|
72
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
72
73
|
template: `<div>${Object.keys(colorThemes).map(templateWithTheme).join('')}</div>`,
|
|
73
74
|
});
|
|
74
75
|
export const Stacked = StackedStory.bind({});
|
|
@@ -2,24 +2,20 @@
|
|
|
2
2
|
<script>
|
|
3
3
|
import Vue from 'vue';
|
|
4
4
|
import { BDropdown } from 'bootstrap-vue';
|
|
5
|
-
import {
|
|
5
|
+
import { selectAll } from 'bootstrap-vue/src/utils/dom';
|
|
6
6
|
import merge from 'lodash/merge';
|
|
7
7
|
import {
|
|
8
8
|
buttonCategoryOptions,
|
|
9
9
|
buttonSizeOptions,
|
|
10
10
|
dropdownVariantOptions,
|
|
11
11
|
} from '../../../utils/constants';
|
|
12
|
+
import { filterVisible } from '../../../utils/utils';
|
|
12
13
|
import { ButtonMixin } from '../../mixins/button_mixin';
|
|
13
14
|
import GlButton from '../button/button.vue';
|
|
14
15
|
import GlIcon from '../icon/icon.vue';
|
|
15
16
|
import GlLoadingIcon from '../loading_icon/loading_icon.vue';
|
|
16
17
|
import GlDropdownDivider from './dropdown_divider.vue';
|
|
17
18
|
|
|
18
|
-
// Return an Array of visible items
|
|
19
|
-
function filterVisible(els) {
|
|
20
|
-
return (els || []).filter(isVisible);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
19
|
const Selector = {
|
|
24
20
|
ITEM_SELECTOR:
|
|
25
21
|
'.dropdown-item:not(.disabled):not([disabled]),.form-control:not(.disabled):not([disabled])',
|
|
@@ -59,6 +59,7 @@ describe('Filtered search', () => {
|
|
|
59
59
|
describe('value manipulation', () => {
|
|
60
60
|
it('creates term when empty', () => {
|
|
61
61
|
createComponent();
|
|
62
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
62
63
|
expect(wrapper.emitted().input[0][0].map(stripId)).toStrictEqual([
|
|
63
64
|
{ type: TERM_TOKEN_TYPE, value: { data: '' } },
|
|
64
65
|
]);
|
|
@@ -202,6 +203,7 @@ describe('Filtered search', () => {
|
|
|
202
203
|
|
|
203
204
|
await nextTick();
|
|
204
205
|
|
|
206
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
205
207
|
expect(wrapper.emitted().input.pop()[0].map(stripId)).toStrictEqual([
|
|
206
208
|
{ type: 'faketoken', value: { data: '' } },
|
|
207
209
|
{ type: TERM_TOKEN_TYPE, value: { data: 'one' } },
|
|
@@ -220,6 +222,7 @@ describe('Filtered search', () => {
|
|
|
220
222
|
|
|
221
223
|
await nextTick();
|
|
222
224
|
|
|
225
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
223
226
|
expect(wrapper.emitted().input.pop()[0].map(stripId)).toStrictEqual([
|
|
224
227
|
{ type: TERM_TOKEN_TYPE, value: { data: 'one' } },
|
|
225
228
|
{ type: TERM_TOKEN_TYPE, value: { data: '' } },
|
|
@@ -335,6 +338,7 @@ describe('Filtered search', () => {
|
|
|
335
338
|
|
|
336
339
|
await nextTick();
|
|
337
340
|
|
|
341
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
338
342
|
expect(wrapper.emitted().input.pop()[0].map(stripId)).toStrictEqual([
|
|
339
343
|
{ type: TERM_TOKEN_TYPE, value: { data: '' } },
|
|
340
344
|
]);
|
|
@@ -348,6 +352,7 @@ describe('Filtered search', () => {
|
|
|
348
352
|
|
|
349
353
|
await nextTick();
|
|
350
354
|
|
|
355
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
351
356
|
expect(wrapper.emitted().input.pop()[0].map(stripId)).toStrictEqual([
|
|
352
357
|
{ type: 'faketoken', value: { data: 'test' } },
|
|
353
358
|
{ type: TERM_TOKEN_TYPE, value: { data: '' } },
|
|
@@ -369,6 +374,7 @@ describe('Filtered search', () => {
|
|
|
369
374
|
|
|
370
375
|
await nextTick();
|
|
371
376
|
|
|
377
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
372
378
|
expect(wrapper.emitted().input.pop()[0].map(stripId)).toStrictEqual([
|
|
373
379
|
{ type: 'faketoken', value: { data: 'test' } },
|
|
374
380
|
{ type: TERM_TOKEN_TYPE, value: { data: '' } },
|
|
@@ -382,6 +388,7 @@ describe('Filtered search', () => {
|
|
|
382
388
|
|
|
383
389
|
await nextTick();
|
|
384
390
|
|
|
391
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
385
392
|
expect(wrapper.emitted().input.pop()[0].map(stripId)).toStrictEqual([
|
|
386
393
|
{ type: TERM_TOKEN_TYPE, value: { data: 'one' } },
|
|
387
394
|
{ type: TERM_TOKEN_TYPE, value: { data: '' } },
|
|
@@ -398,6 +405,7 @@ describe('Filtered search', () => {
|
|
|
398
405
|
await nextTick();
|
|
399
406
|
|
|
400
407
|
expect(wrapper.findAllComponents(GlFilteredSearchTerm).at(2).props('active')).toBe(true);
|
|
408
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
401
409
|
expect(wrapper.emitted().input.pop()[0].map(stripId)).toStrictEqual([
|
|
402
410
|
{ type: TERM_TOKEN_TYPE, value: { data: 'one' } },
|
|
403
411
|
{ type: TERM_TOKEN_TYPE, value: { data: 'two' } },
|
|
@@ -415,6 +423,7 @@ describe('Filtered search', () => {
|
|
|
415
423
|
|
|
416
424
|
await nextTick();
|
|
417
425
|
|
|
426
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
418
427
|
expect(wrapper.emitted().input.pop()[0].map(stripId)).toStrictEqual([
|
|
419
428
|
{ type: TERM_TOKEN_TYPE, value: { data: 'one' } },
|
|
420
429
|
{ type: TERM_TOKEN_TYPE, value: { data: 'foo' } },
|
|
@@ -445,6 +454,7 @@ describe('Filtered search', () => {
|
|
|
445
454
|
});
|
|
446
455
|
wrapper.findComponent(GlFilteredSearchTerm).vm.$emit('submit');
|
|
447
456
|
expect(wrapper.emitted('submit')).toBeDefined();
|
|
457
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
448
458
|
expect(wrapper.emitted().submit[0][0].map(stripId)).toStrictEqual([
|
|
449
459
|
'one two',
|
|
450
460
|
{ type: 'faketoken', value: { data: 'smth' } },
|
|
@@ -466,7 +476,7 @@ describe('Filtered search', () => {
|
|
|
466
476
|
});
|
|
467
477
|
|
|
468
478
|
it('passes `searchButtonAttributes` prop to `GlSearchBoxByClick`', () => {
|
|
469
|
-
const searchButtonAttributes = { 'data-
|
|
479
|
+
const searchButtonAttributes = { 'data-prop': 'foo-bar' };
|
|
470
480
|
|
|
471
481
|
createComponent({ searchButtonAttributes });
|
|
472
482
|
|
|
@@ -474,7 +484,7 @@ describe('Filtered search', () => {
|
|
|
474
484
|
});
|
|
475
485
|
|
|
476
486
|
it('passes `searchInputAttributes` prop to search term', async () => {
|
|
477
|
-
const searchInputAttributes = { 'data-
|
|
487
|
+
const searchInputAttributes = { 'data-prop': 'foo-bar' };
|
|
478
488
|
|
|
479
489
|
createComponent({
|
|
480
490
|
value: ['one'],
|
|
@@ -505,6 +515,7 @@ describe('Filtered search', () => {
|
|
|
505
515
|
});
|
|
506
516
|
await nextTick();
|
|
507
517
|
|
|
518
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
508
519
|
expect(wrapper.findComponent(GlFilteredSearchTerm).props('currentValue').map(stripId)).toEqual([
|
|
509
520
|
{ type: 'filtered-search-term', value: { data: 'one' } },
|
|
510
521
|
{ type: 'filtered-search-term', value: { data: '' } },
|
|
@@ -15,7 +15,7 @@ const pointerClass = 'gl-cursor-pointer';
|
|
|
15
15
|
describe('Filtered search term', () => {
|
|
16
16
|
let wrapper;
|
|
17
17
|
|
|
18
|
-
const searchInputAttributes = { 'data-
|
|
18
|
+
const searchInputAttributes = { 'data-testid': 'foo-bar', 'data-prop': 'foo-property' };
|
|
19
19
|
|
|
20
20
|
const defaultProps = {
|
|
21
21
|
availableTokens: [],
|
|
@@ -182,9 +182,7 @@ describe('Filtered search term', () => {
|
|
|
182
182
|
searchInputAttributes,
|
|
183
183
|
});
|
|
184
184
|
|
|
185
|
-
expect(findSearchInput().attributes('data-
|
|
186
|
-
searchInputAttributes['data-qa-selector']
|
|
187
|
-
);
|
|
185
|
+
expect(findSearchInput().attributes('data-prop')).toBe(searchInputAttributes['data-prop']);
|
|
188
186
|
});
|
|
189
187
|
|
|
190
188
|
it('activates and deactivates when the input is focused/blurred', async () => {
|
|
@@ -10,7 +10,7 @@ const OPTIONS = [
|
|
|
10
10
|
describe('Filtered search token segment', () => {
|
|
11
11
|
let wrapper;
|
|
12
12
|
|
|
13
|
-
const searchInputAttributes = { 'data-
|
|
13
|
+
const searchInputAttributes = { 'data-prop': 'foo-bar' };
|
|
14
14
|
|
|
15
15
|
beforeAll(() => {
|
|
16
16
|
if (!HTMLElement.prototype.scrollIntoView) {
|
|
@@ -336,7 +336,7 @@ describe('Filtered search token segment', () => {
|
|
|
336
336
|
});
|
|
337
337
|
|
|
338
338
|
it('does not add `searchInputAttributes` prop to search token segment', () => {
|
|
339
|
-
expect(wrapper.attributes('data-
|
|
339
|
+
expect(wrapper.attributes('data-prop')).toBe(undefined);
|
|
340
340
|
});
|
|
341
341
|
}
|
|
342
342
|
);
|
|
@@ -353,9 +353,7 @@ describe('Filtered search token segment', () => {
|
|
|
353
353
|
});
|
|
354
354
|
|
|
355
355
|
it('adds `searchInputAttributes` prop to search token segment', () => {
|
|
356
|
-
expect(wrapper.attributes('data-
|
|
357
|
-
searchInputAttributes['data-qa-selector']
|
|
358
|
-
);
|
|
356
|
+
expect(wrapper.attributes('data-prop')).toBe(searchInputAttributes['data-prop']);
|
|
359
357
|
});
|
|
360
358
|
});
|
|
361
359
|
|
|
@@ -363,8 +361,8 @@ describe('Filtered search token segment', () => {
|
|
|
363
361
|
it('adds `searchInputAttributes` prop to search token segment input', () => {
|
|
364
362
|
createComponent({ active: true, value: 'something', searchInputAttributes });
|
|
365
363
|
|
|
366
|
-
expect(wrapper.find('input').attributes('data-
|
|
367
|
-
searchInputAttributes['data-
|
|
364
|
+
expect(wrapper.find('input').attributes('data-prop')).toBe(
|
|
365
|
+
searchInputAttributes['data-prop']
|
|
368
366
|
);
|
|
369
367
|
});
|
|
370
368
|
|
|
@@ -24,7 +24,8 @@ const isVNodeEmpty = (vnode) => {
|
|
|
24
24
|
if (isVue3Fragment(vnode)) {
|
|
25
25
|
// vnode.children might be an array or single node in edge cases
|
|
26
26
|
return Array.isArray(vnode.children)
|
|
27
|
-
?
|
|
27
|
+
? // eslint-disable-next-line unicorn/no-array-callback-reference
|
|
28
|
+
vnode.children.every(isVNodeEmpty)
|
|
28
29
|
: isVNodeEmpty(vnode.children);
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -41,6 +42,7 @@ const isSlotNotEmpty = (slot) => {
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
const vnodes = typeof slot === 'function' ? slot() : slot;
|
|
45
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
44
46
|
return !(Array.isArray(vnodes) ? vnodes.every(isVNodeEmpty) : isVNodeEmpty(vnodes));
|
|
45
47
|
};
|
|
46
48
|
|
|
@@ -39,7 +39,7 @@ export default {
|
|
|
39
39
|
</script>
|
|
40
40
|
|
|
41
41
|
<template>
|
|
42
|
-
<div :class="rootClass" :data-
|
|
42
|
+
<div :class="rootClass" :data-testid="`${$options.qaPrefix}${option.value}`">
|
|
43
43
|
<gl-form-checkbox
|
|
44
44
|
:checked="node.isChecked"
|
|
45
45
|
:indeterminate="node.isIndeterminate"
|
|
@@ -87,7 +87,7 @@ describe('GlFormCheckboxTree', () => {
|
|
|
87
87
|
const findCheckboxes = (el = wrapper) => el.findAllComponents(GlFormCheckbox);
|
|
88
88
|
const countIndeterminate = () => wrapper.findAll('.js-is-indeterminate').length || 0;
|
|
89
89
|
const countChecked = () => wrapper.findAll('.js-is-checked').length || 0;
|
|
90
|
-
const findCheckboxByValue = (value) => wrapper.find(`[data-
|
|
90
|
+
const findCheckboxByValue = (value) => wrapper.find(`[data-testid="${QA_PREFIX}${value}"]`);
|
|
91
91
|
const getCheckboxesCount = (el) => findCheckboxes(el).length;
|
|
92
92
|
const findCheckboxInput = (checkbox) => checkbox.find('input[type="checkbox"]');
|
|
93
93
|
const expectCheckboxUnchecked = (checkbox) => {
|
|
@@ -152,7 +152,7 @@ describe('GlFormCheckboxTree', () => {
|
|
|
152
152
|
|
|
153
153
|
beforeEach(async () => {
|
|
154
154
|
createWrapper({ options: getOptions(shape) });
|
|
155
|
-
checkbox = wrapper.find(`[data-
|
|
155
|
+
checkbox = wrapper.find(`[data-testid="${QA_PREFIX}${boxToCheck}"]`);
|
|
156
156
|
await checkbox.find('input').setChecked();
|
|
157
157
|
});
|
|
158
158
|
|
|
@@ -78,6 +78,7 @@ describe('GlFormFields', () => {
|
|
|
78
78
|
};
|
|
79
79
|
};
|
|
80
80
|
const findFormGroups = () => wrapper.findAllComponents(GlFormGroup).wrappers;
|
|
81
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
81
82
|
const findFormGroupsAsData = () => findFormGroups().map(mapFormGroupToData);
|
|
82
83
|
const findFormGroupFromLabel = (label) =>
|
|
83
84
|
wrapper.findAllComponents(GlFormGroup).wrappers.find((x) => x.attributes('label') === label);
|
|
@@ -36,7 +36,7 @@ describe('base dropdown', () => {
|
|
|
36
36
|
...propsData,
|
|
37
37
|
},
|
|
38
38
|
slots: {
|
|
39
|
-
default: `<div class="${GL_DROPDOWN_CONTENTS_CLASS}"
|
|
39
|
+
default: `<div class="${GL_DROPDOWN_CONTENTS_CLASS}"><button /></div>`,
|
|
40
40
|
...slots,
|
|
41
41
|
},
|
|
42
42
|
attachTo: document.body,
|
|
@@ -57,6 +57,8 @@ describe('base dropdown', () => {
|
|
|
57
57
|
const findDropdownToggleText = () => findDefaultDropdownToggle().find('.gl-button-text');
|
|
58
58
|
const findDropdownMenu = () => wrapper.find('.gl-new-dropdown-panel');
|
|
59
59
|
|
|
60
|
+
const moveFocusWithinDropdown = () => findDropdownMenu().find('button').element.focus();
|
|
61
|
+
|
|
60
62
|
describe('Floating UI instance', () => {
|
|
61
63
|
it("starts Floating UI's when opening the dropdown", async () => {
|
|
62
64
|
buildWrapper();
|
|
@@ -324,14 +326,47 @@ describe('base dropdown', () => {
|
|
|
324
326
|
expect(menu.classes('gl-display-block!')).toBe(true);
|
|
325
327
|
expect(toggle.attributes('aria-expanded')).toBe('true');
|
|
326
328
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
+
moveFocusWithinDropdown();
|
|
330
|
+
|
|
331
|
+
// close menu by pressing ESC key
|
|
329
332
|
await menu.trigger('keydown.esc');
|
|
333
|
+
|
|
330
334
|
expect(menu.classes('gl-display-block!')).toBe(false);
|
|
331
335
|
expect(toggle.attributes('aria-expanded')).toBeUndefined();
|
|
332
336
|
expect(wrapper.emitted(GL_DROPDOWN_HIDDEN)).toHaveLength(1);
|
|
333
337
|
expect(toggle.element).toHaveFocus();
|
|
334
338
|
});
|
|
339
|
+
|
|
340
|
+
describe('when the consumer takes over the focus', () => {
|
|
341
|
+
let consumerButton;
|
|
342
|
+
|
|
343
|
+
beforeEach(() => {
|
|
344
|
+
consumerButton = document.createElement('button');
|
|
345
|
+
document.body.appendChild(consumerButton);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
afterEach(() => {
|
|
349
|
+
consumerButton.remove();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('does not steal the focus back from the consumer when closing the dropdown', async () => {
|
|
353
|
+
const toggle = findDefaultDropdownToggle();
|
|
354
|
+
const menu = findDropdownMenu();
|
|
355
|
+
|
|
356
|
+
// open menu clicking toggle btn
|
|
357
|
+
await toggle.trigger('click');
|
|
358
|
+
|
|
359
|
+
moveFocusWithinDropdown();
|
|
360
|
+
|
|
361
|
+
// consumer focuses some element
|
|
362
|
+
consumerButton.focus();
|
|
363
|
+
|
|
364
|
+
// close menu by pressing ESC key
|
|
365
|
+
await menu.trigger('keydown.esc');
|
|
366
|
+
|
|
367
|
+
expect(consumerButton).toHaveFocus();
|
|
368
|
+
});
|
|
369
|
+
});
|
|
335
370
|
});
|
|
336
371
|
|
|
337
372
|
describe('beforeClose event', () => {
|
|
@@ -353,7 +388,7 @@ describe('base dropdown', () => {
|
|
|
353
388
|
|
|
354
389
|
await toggle.trigger('click');
|
|
355
390
|
|
|
356
|
-
|
|
391
|
+
moveFocusWithinDropdown();
|
|
357
392
|
await menu.trigger('keydown.esc');
|
|
358
393
|
expect(menu.classes('gl-display-block!')).toBe(true);
|
|
359
394
|
expect(toggle.attributes('aria-expanded')).toBeDefined();
|
|
@@ -365,6 +400,7 @@ describe('base dropdown', () => {
|
|
|
365
400
|
const toggle = findDefaultDropdownToggle();
|
|
366
401
|
const menu = findDropdownMenu();
|
|
367
402
|
await toggle.trigger('click');
|
|
403
|
+
moveFocusWithinDropdown();
|
|
368
404
|
await menu.trigger('keydown.esc');
|
|
369
405
|
expect(event.type).toBe('keydown');
|
|
370
406
|
});
|
|
@@ -444,6 +480,7 @@ describe('base dropdown', () => {
|
|
|
444
480
|
expect(menu.classes('gl-display-block!')).toBe(true);
|
|
445
481
|
|
|
446
482
|
// close menu pressing ESC on it
|
|
483
|
+
moveFocusWithinDropdown();
|
|
447
484
|
await menu.trigger('keydown.esc');
|
|
448
485
|
expect(menu.classes('gl-display-block!')).toBe(false);
|
|
449
486
|
expect(firstToggleChild.attributes('aria-expanded')).toBe('false');
|
|
@@ -382,11 +382,23 @@ export default {
|
|
|
382
382
|
}
|
|
383
383
|
this.toggle(event);
|
|
384
384
|
},
|
|
385
|
+
/**
|
|
386
|
+
* Closes the dropdown and returns the focus to the toggle unless it has has moved outside
|
|
387
|
+
* of the dropdown, meaning that the consumer needed to put some other element in focus.
|
|
388
|
+
*
|
|
389
|
+
* @param {KeyboardEvent?} event The keyboard event that caused the dropdown to close.
|
|
390
|
+
*/
|
|
385
391
|
async closeAndFocus(event) {
|
|
386
392
|
if (!this.visible) {
|
|
387
393
|
return;
|
|
388
394
|
}
|
|
395
|
+
|
|
389
396
|
const hasToggled = await this.toggle(event);
|
|
397
|
+
|
|
398
|
+
if (!this.$el.contains(document.activeElement)) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
390
402
|
if (hasToggled) {
|
|
391
403
|
this.focusToggle();
|
|
392
404
|
}
|
|
@@ -9,8 +9,10 @@ const isGroup = (group) =>
|
|
|
9
9
|
Boolean(group) &&
|
|
10
10
|
Array.isArray(group.items) &&
|
|
11
11
|
Boolean(group.items.length) &&
|
|
12
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
12
13
|
group.items.every(isItem);
|
|
13
14
|
|
|
15
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
14
16
|
const itemsValidator = (items) => items.every(isItem) || items.every(isGroup);
|
|
15
17
|
|
|
16
18
|
const isListItem = (tag) =>
|
|
@@ -48,6 +48,7 @@ describe('GlCollapsibleListbox', () => {
|
|
|
48
48
|
const findListContainer = () => wrapper.find('[role="listbox"]');
|
|
49
49
|
const findListboxItems = (root = wrapper) => root.findAllComponents(GlListboxItem);
|
|
50
50
|
const findListboxGroups = () => wrapper.findAllComponents(GlListboxGroup);
|
|
51
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
51
52
|
const findListItem = (index) => findListboxItems().at(index).find(ITEM_SELECTOR);
|
|
52
53
|
const findHeaderText = () => wrapper.find("[data-testid='listbox-header-text']");
|
|
53
54
|
const findSearchBox = () => wrapper.find("[data-testid='listbox-search-input']");
|
|
@@ -241,6 +242,7 @@ describe('GlCollapsibleListbox', () => {
|
|
|
241
242
|
|
|
242
243
|
it('should focus the first selected item', async () => {
|
|
243
244
|
await showDropdown();
|
|
245
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
244
246
|
expect(findListboxItems().at(1).find(ITEM_SELECTOR).element).toHaveFocus();
|
|
245
247
|
});
|
|
246
248
|
|
|
@@ -388,9 +388,13 @@ export const Groups = makeGroupedExample({
|
|
|
388
388
|
const isSelected = (option) => this.selected.includes(option.value);
|
|
389
389
|
const notSelected = (option) => !isSelected(option);
|
|
390
390
|
|
|
391
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
391
392
|
const selectedBranches = mockGroups[0].options.filter(isSelected);
|
|
393
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
392
394
|
const availableBranches = mockGroups[0].options.filter(notSelected);
|
|
395
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
393
396
|
const selectedTags = mockGroups[1].options.filter(isSelected);
|
|
397
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
394
398
|
const availableTags = mockGroups[1].options.filter(notSelected);
|
|
395
399
|
|
|
396
400
|
return [
|
|
@@ -3,17 +3,20 @@ import isString from 'lodash/isString';
|
|
|
3
3
|
|
|
4
4
|
const isOption = (item) => Boolean(item) && (isString(item.value) || isNumber(item.value));
|
|
5
5
|
|
|
6
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
6
7
|
const isGroup = ({ options } = {}) => Array.isArray(options) && options.every(isOption);
|
|
7
8
|
|
|
8
9
|
const hasNoDuplicates = (array) => array.length === new Set(array).size;
|
|
9
10
|
|
|
10
11
|
const flattenedOptions = (items) => items.flatMap((item) => (isOption(item) ? item : item.options));
|
|
11
12
|
|
|
13
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
12
14
|
const isAllOptionsOrAllGroups = (items) => items.every(isOption) || items.every(isGroup);
|
|
13
15
|
|
|
14
16
|
const hasUniqueValues = (items) =>
|
|
15
17
|
hasNoDuplicates(flattenedOptions(items).map(({ value }) => value));
|
|
16
18
|
|
|
19
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
17
20
|
const hasUniqueGroups = (items) => hasNoDuplicates(items.filter(isGroup).map(({ text }) => text));
|
|
18
21
|
|
|
19
22
|
const itemsValidator = (items) =>
|
|
@@ -162,12 +162,10 @@ describe('search box by click component', () => {
|
|
|
162
162
|
});
|
|
163
163
|
|
|
164
164
|
it('adds `searchButtonAttributes` prop to search button', () => {
|
|
165
|
-
const searchButtonAttributes = { 'data-
|
|
165
|
+
const searchButtonAttributes = { 'data-prop': 'foo-bar' };
|
|
166
166
|
|
|
167
167
|
createComponent({ searchButtonAttributes });
|
|
168
168
|
|
|
169
|
-
expect(findSearchButton().attributes('data-
|
|
170
|
-
searchButtonAttributes['data-qa-selector']
|
|
171
|
-
);
|
|
169
|
+
expect(findSearchButton().attributes('data-prop')).toBe(searchButtonAttributes['data-prop']);
|
|
172
170
|
});
|
|
173
171
|
});
|
|
@@ -254,12 +254,12 @@ describe('GlTokenSelector', () => {
|
|
|
254
254
|
createComponent({
|
|
255
255
|
propsData: {
|
|
256
256
|
textInputAttrs: {
|
|
257
|
-
'data-
|
|
257
|
+
'data-prop': 'foo_bar',
|
|
258
258
|
},
|
|
259
259
|
},
|
|
260
260
|
});
|
|
261
261
|
|
|
262
|
-
expect(findTextInput().attributes('data-
|
|
262
|
+
expect(findTextInput().attributes('data-prop')).toBe('foo_bar');
|
|
263
263
|
});
|
|
264
264
|
});
|
|
265
265
|
|
|
@@ -91,7 +91,7 @@ export default {
|
|
|
91
91
|
default: null,
|
|
92
92
|
},
|
|
93
93
|
/**
|
|
94
|
-
* HTML attributes to add to the text input. Helpful for adding `data-testid`
|
|
94
|
+
* HTML attributes to add to the text input. Helpful for adding `data-testid` attributes
|
|
95
95
|
*/
|
|
96
96
|
textInputAttrs: {
|
|
97
97
|
type: Object,
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from '../../../utils/charts/mock_data';
|
|
8
8
|
import { toolbox } from '../../../utils/charts/story_config';
|
|
9
9
|
|
|
10
|
-
const template = `
|
|
10
|
+
const template = (content = '') => `
|
|
11
11
|
<gl-column-chart
|
|
12
12
|
:bars="bars"
|
|
13
13
|
:lines="lines"
|
|
@@ -18,7 +18,9 @@ const template = `
|
|
|
18
18
|
:x-axis-title="xAxisTitle"
|
|
19
19
|
:x-axis-type="xAxisType"
|
|
20
20
|
:height="height"
|
|
21
|
-
|
|
21
|
+
>
|
|
22
|
+
${content}
|
|
23
|
+
</gl-column-chart>
|
|
22
24
|
`;
|
|
23
25
|
|
|
24
26
|
const generateProps = ({
|
|
@@ -46,7 +48,7 @@ const generateProps = ({
|
|
|
46
48
|
const Template = (args, { argTypes }) => ({
|
|
47
49
|
components: { GlColumnChart },
|
|
48
50
|
props: Object.keys(argTypes),
|
|
49
|
-
template,
|
|
51
|
+
template: template(),
|
|
50
52
|
});
|
|
51
53
|
|
|
52
54
|
export const Default = Template.bind({});
|
|
@@ -90,6 +92,19 @@ SecondaryYAxisLine.args = generateProps({
|
|
|
90
92
|
secondaryDataTitle: 'New line data',
|
|
91
93
|
});
|
|
92
94
|
|
|
95
|
+
export const WithCustomTooltip = (args, { argTypes }) => ({
|
|
96
|
+
components: { GlColumnChart },
|
|
97
|
+
props: Object.keys(argTypes),
|
|
98
|
+
template: template(`
|
|
99
|
+
<template #tooltip-title="{ params }">Custom tooltip title: {{params && params.value}}</template>
|
|
100
|
+
<template #tooltip-content="{ params }">
|
|
101
|
+
<div v-for="p in params && params.seriesData">Wow so custom: {{p.seriesName}}: {{p.value[1]}}</div>
|
|
102
|
+
</template>
|
|
103
|
+
`),
|
|
104
|
+
});
|
|
105
|
+
WithCustomTooltip.args = generateProps();
|
|
106
|
+
WithCustomTooltip.tags = ['skip-visual-test'];
|
|
107
|
+
|
|
93
108
|
export default {
|
|
94
109
|
title: 'charts/column-chart',
|
|
95
110
|
component: GlColumnChart,
|
|
@@ -184,6 +184,21 @@ export default {
|
|
|
184
184
|
v-on="$listeners"
|
|
185
185
|
@created="onCreated"
|
|
186
186
|
/>
|
|
187
|
-
<chart-tooltip
|
|
187
|
+
<chart-tooltip
|
|
188
|
+
v-if="chart"
|
|
189
|
+
ref="dataTooltip"
|
|
190
|
+
:chart="chart"
|
|
191
|
+
:use-default-tooltip-formatter="true"
|
|
192
|
+
>
|
|
193
|
+
<template v-if="$scopedSlots['tooltip-title']" #title="scope">
|
|
194
|
+
<slot name="tooltip-title" v-bind="scope"></slot>
|
|
195
|
+
</template>
|
|
196
|
+
<template v-if="$scopedSlots['tooltip-content']" #default="scope">
|
|
197
|
+
<slot name="tooltip-content" v-bind="scope"></slot>
|
|
198
|
+
</template>
|
|
199
|
+
<template v-if="$scopedSlots['tooltip-value']" #tooltip-value="scope">
|
|
200
|
+
<slot name="tooltip-value" v-bind="scope"></slot>
|
|
201
|
+
</template>
|
|
202
|
+
</chart-tooltip>
|
|
188
203
|
</div>
|
|
189
204
|
</template>
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { nextTick } from 'vue';
|
|
1
2
|
import { shallowMount } from '@vue/test-utils';
|
|
2
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createMockChartInstance,
|
|
5
|
+
ChartTooltipStub,
|
|
6
|
+
chartTooltipStubData,
|
|
7
|
+
} from '~helpers/chart_stubs';
|
|
3
8
|
import { expectHeightAutoClasses } from '~helpers/chart_height';
|
|
4
9
|
import {
|
|
5
10
|
mockDefaultLineData,
|
|
@@ -8,7 +13,6 @@ import {
|
|
|
8
13
|
mockDefaultStackedBarData,
|
|
9
14
|
} from '../../../utils/charts/mock_data';
|
|
10
15
|
import Chart from '../chart/chart.vue';
|
|
11
|
-
import ChartTooltip from '../tooltip/tooltip.vue';
|
|
12
16
|
import ColumnChart from './column.vue';
|
|
13
17
|
|
|
14
18
|
let mockChartInstance;
|
|
@@ -28,42 +32,45 @@ describe('column chart component', () => {
|
|
|
28
32
|
|
|
29
33
|
const chartItemClickedSpy = jest.fn();
|
|
30
34
|
const findChart = () => wrapper.findComponent(Chart);
|
|
31
|
-
const findChartTooltip = () => wrapper.findComponent(
|
|
35
|
+
const findChartTooltip = () => wrapper.findComponent({ ref: 'dataTooltip' });
|
|
32
36
|
|
|
33
37
|
const emitChartCreated = () => findChart().vm.$emit('created', mockChartInstance);
|
|
34
38
|
|
|
35
|
-
const factory = (props = defaultChartProps,
|
|
39
|
+
const factory = (props = defaultChartProps, options = {}) => {
|
|
36
40
|
wrapper = shallowMount(ColumnChart, {
|
|
37
41
|
propsData: { ...props },
|
|
38
42
|
listeners: {
|
|
39
43
|
chartItemClicked: chartItemClickedSpy,
|
|
40
44
|
},
|
|
41
|
-
|
|
45
|
+
...options,
|
|
42
46
|
});
|
|
43
47
|
};
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
describe('mounted', () => {
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
factory();
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
mockChartInstance = createMockChartInstance();
|
|
54
|
+
emitChartCreated();
|
|
55
|
+
});
|
|
51
56
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
it('emits "created" when onCreated is called', () => {
|
|
58
|
+
expect(wrapper.emitted('created')).toHaveLength(1);
|
|
59
|
+
});
|
|
55
60
|
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
it('calls event listener when "chartItemClicked" is emitted on the Chart component', () => {
|
|
62
|
+
findChart().vm.$emit('chartItemClicked');
|
|
58
63
|
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
expect(chartItemClickedSpy).toHaveBeenCalled();
|
|
65
|
+
});
|
|
61
66
|
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
it('should correctly render the chart', () => {
|
|
68
|
+
const chart = findChart();
|
|
64
69
|
|
|
65
|
-
|
|
70
|
+
expect(chart.props('options')).toMatchSnapshot();
|
|
71
|
+
});
|
|
66
72
|
});
|
|
73
|
+
|
|
67
74
|
describe('with line data provided', () => {
|
|
68
75
|
beforeEach(() => {
|
|
69
76
|
factory({
|
|
@@ -120,11 +127,90 @@ describe('column chart component', () => {
|
|
|
120
127
|
|
|
121
128
|
describe('tooltip', () => {
|
|
122
129
|
it('configures chart tooltip', async () => {
|
|
130
|
+
factory(defaultChartProps, {
|
|
131
|
+
stubs: {
|
|
132
|
+
'chart-tooltip': ChartTooltipStub,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
mockChartInstance = createMockChartInstance();
|
|
136
|
+
emitChartCreated();
|
|
137
|
+
await nextTick();
|
|
138
|
+
|
|
123
139
|
expect(findChartTooltip().props()).toMatchObject({
|
|
124
140
|
chart: mockChartInstance,
|
|
125
141
|
useDefaultTooltipFormatter: true,
|
|
126
142
|
});
|
|
127
143
|
});
|
|
144
|
+
|
|
145
|
+
describe('custom tooltip slots', () => {
|
|
146
|
+
const { params, title, content } = chartTooltipStubData;
|
|
147
|
+
|
|
148
|
+
it('customizes value', async () => {
|
|
149
|
+
const tooltipValueSlot = jest.fn().mockReturnValue('Custom tooltip value');
|
|
150
|
+
|
|
151
|
+
factory(defaultChartProps, {
|
|
152
|
+
stubs: {
|
|
153
|
+
'chart-tooltip': ChartTooltipStub,
|
|
154
|
+
},
|
|
155
|
+
scopedSlots: {
|
|
156
|
+
'tooltip-value': tooltipValueSlot,
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
mockChartInstance = createMockChartInstance();
|
|
161
|
+
emitChartCreated();
|
|
162
|
+
await nextTick();
|
|
163
|
+
|
|
164
|
+
expect(tooltipValueSlot).toHaveBeenCalledWith({ value: 9 });
|
|
165
|
+
expect(findChartTooltip().text()).toBe('Custom tooltip value');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('customizes title', async () => {
|
|
169
|
+
const tooltipTitleSlot = jest.fn().mockReturnValue('Custom tooltip title');
|
|
170
|
+
|
|
171
|
+
factory(defaultChartProps, {
|
|
172
|
+
stubs: {
|
|
173
|
+
'chart-tooltip': ChartTooltipStub,
|
|
174
|
+
},
|
|
175
|
+
scopedSlots: {
|
|
176
|
+
'tooltip-title': tooltipTitleSlot,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
mockChartInstance = createMockChartInstance();
|
|
181
|
+
emitChartCreated();
|
|
182
|
+
await nextTick();
|
|
183
|
+
|
|
184
|
+
expect(tooltipTitleSlot).toHaveBeenCalledWith({
|
|
185
|
+
params,
|
|
186
|
+
title,
|
|
187
|
+
});
|
|
188
|
+
expect(findChartTooltip().text()).toBe('Custom tooltip title');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('customizes content', async () => {
|
|
192
|
+
const tooltipContentSlot = jest.fn().mockReturnValue('Custom tooltip content');
|
|
193
|
+
|
|
194
|
+
factory(defaultChartProps, {
|
|
195
|
+
stubs: {
|
|
196
|
+
'chart-tooltip': ChartTooltipStub,
|
|
197
|
+
},
|
|
198
|
+
scopedSlots: {
|
|
199
|
+
'tooltip-content': tooltipContentSlot,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
mockChartInstance = createMockChartInstance();
|
|
204
|
+
emitChartCreated();
|
|
205
|
+
await nextTick();
|
|
206
|
+
|
|
207
|
+
expect(tooltipContentSlot).toHaveBeenCalledWith({
|
|
208
|
+
params,
|
|
209
|
+
content,
|
|
210
|
+
});
|
|
211
|
+
expect(findChartTooltip().text()).toBe('Custom tooltip content');
|
|
212
|
+
});
|
|
213
|
+
});
|
|
128
214
|
});
|
|
129
215
|
|
|
130
216
|
describe('height', () => {
|
|
@@ -98,6 +98,7 @@ describe('IntersectionObserver', () => {
|
|
|
98
98
|
triggerIntersectionObserver(entry);
|
|
99
99
|
|
|
100
100
|
expect(allWrappers).toHaveLength(2);
|
|
101
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
101
102
|
expect(allWrappers.map((x) => x.emitted())).toEqual(allWrappers.map(createExpectation));
|
|
102
103
|
});
|
|
103
104
|
});
|
|
@@ -23,6 +23,7 @@ describe('Intersperse Component', () => {
|
|
|
23
23
|
createComponent(defaultSlotContent);
|
|
24
24
|
|
|
25
25
|
selectorsToCheck.forEach((selector) => {
|
|
26
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
26
27
|
expect(wrapper.find(selector).exists()).toBe(true);
|
|
27
28
|
});
|
|
28
29
|
}
|
|
@@ -68,6 +68,7 @@ export default {
|
|
|
68
68
|
default: undefined,
|
|
69
69
|
validator: (value) =>
|
|
70
70
|
Object.values(value).every(
|
|
71
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
71
72
|
(tagPair) => Array.isArray(tagPair) && tagPair.length === 2 && tagPair.every(isString)
|
|
72
73
|
),
|
|
73
74
|
},
|
|
@@ -13,11 +13,13 @@ function isEmpty(vnode) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
if (Array.isArray(vnode)) {
|
|
16
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
16
17
|
return vnode.every(isEmpty);
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
if (Fragment && vnode.type === Fragment) {
|
|
20
21
|
// Vue.js 3 fragment, check children
|
|
22
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
21
23
|
return vnode.children.every(isEmpty);
|
|
22
24
|
}
|
|
23
25
|
|
|
@@ -33,5 +35,6 @@ export function isSlotEmpty(vueInstance, slot, slotArgs) {
|
|
|
33
35
|
callIfNeeded(vueInstance.$slots[slot] || vueInstance.$scopedSlots[slot], slotArgs)
|
|
34
36
|
: vueInstance.$scopedSlots[slot]?.(slotArgs);
|
|
35
37
|
|
|
38
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
36
39
|
return isEmpty(slotContent);
|
|
37
40
|
}
|
|
@@ -9,7 +9,7 @@ export const addition = (a, b) => a + b;
|
|
|
9
9
|
* Returns the sum of all arguments
|
|
10
10
|
* @param {...Number} numbers
|
|
11
11
|
*/
|
|
12
|
-
export const sum = (...numbers) => numbers.reduce(addition);
|
|
12
|
+
export const sum = (...numbers) => numbers.reduce(addition); // eslint-disable-line unicorn/no-array-callback-reference
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Returns the average of all arguments
|
package/src/utils/utils.js
CHANGED