@gitlab/ui 78.2.1 → 78.2.3
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 +15 -0
- package/README.md +5 -0
- package/dist/components/base/new_dropdowns/base_dropdown/base_dropdown.js +3 -2
- package/dist/components/base/table/table.js +19 -5
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -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/package.json +2 -2
- package/src/components/base/filtered_search/filtered_search.stories.js +1 -1
- package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.spec.js +26 -0
- package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.vue +3 -1
- package/src/components/base/new_dropdowns/listbox/listbox.spec.js +14 -0
- package/src/components/base/table/table.scss +11 -4
- package/src/components/base/table/table.spec.js +58 -9
- package/src/components/base/table/table.stories.js +1 -0
- package/src/components/base/table/table.vue +25 -20
- package/src/components/experimental/duo/chat/duo_chat.spec.js +33 -46
package/dist/tokens/js/tokens.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitlab/ui",
|
|
3
|
-
"version": "78.2.
|
|
3
|
+
"version": "78.2.3",
|
|
4
4
|
"description": "GitLab UI Components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"@gitlab/eslint-plugin": "19.4.0",
|
|
103
103
|
"@gitlab/fonts": "^1.3.0",
|
|
104
104
|
"@gitlab/stylelint-config": "6.1.0",
|
|
105
|
-
"@gitlab/svgs": "3.
|
|
105
|
+
"@gitlab/svgs": "3.90.0",
|
|
106
106
|
"@rollup/plugin-commonjs": "^11.1.0",
|
|
107
107
|
"@rollup/plugin-node-resolve": "^7.1.3",
|
|
108
108
|
"@rollup/plugin-replace": "^2.3.2",
|
|
@@ -349,7 +349,7 @@ const tokens = [
|
|
|
349
349
|
token: UserToken,
|
|
350
350
|
},
|
|
351
351
|
{ type: 'user', icon: 'user', title: 'Assignee', dataType: 'user', token: UserToken },
|
|
352
|
-
{ type: 'milestone', icon: '
|
|
352
|
+
{ type: 'milestone', icon: 'milestone', title: 'Milestone', unique: true, token: MilestoneToken },
|
|
353
353
|
{ type: 'label', icon: 'labels', title: 'Label', token: LabelToken },
|
|
354
354
|
{ type: 'weight', icon: 'weight', title: 'Weight', unique: true, token: GlFilteredSearchToken },
|
|
355
355
|
{
|
|
@@ -337,6 +337,20 @@ describe('base dropdown', () => {
|
|
|
337
337
|
expect(toggle.element).toHaveFocus();
|
|
338
338
|
});
|
|
339
339
|
|
|
340
|
+
it('should close menu on Escape when focus is on toggle', async () => {
|
|
341
|
+
const toggle = findDefaultDropdownToggle();
|
|
342
|
+
const menu = findDropdownMenu();
|
|
343
|
+
|
|
344
|
+
await toggle.trigger('click');
|
|
345
|
+
expect(menu.classes('gl-display-block!')).toBe(true);
|
|
346
|
+
expect(toggle.attributes('aria-expanded')).toBe('true');
|
|
347
|
+
|
|
348
|
+
await toggle.trigger('keydown.esc');
|
|
349
|
+
expect(menu.classes('gl-display-block!')).toBe(false);
|
|
350
|
+
expect(toggle.attributes('aria-expanded')).toBe('false');
|
|
351
|
+
expect(wrapper.emitted(GL_DROPDOWN_HIDDEN)).toHaveLength(1);
|
|
352
|
+
});
|
|
353
|
+
|
|
340
354
|
describe('when the consumer takes over the focus', () => {
|
|
341
355
|
let consumerButton;
|
|
342
356
|
|
|
@@ -487,6 +501,18 @@ describe('base dropdown', () => {
|
|
|
487
501
|
expect(wrapper.emitted(GL_DROPDOWN_HIDDEN)).toHaveLength(1);
|
|
488
502
|
expect(toggle.find(`[data-testid="${customToggleTestId}"]`).element).toHaveFocus();
|
|
489
503
|
});
|
|
504
|
+
|
|
505
|
+
it('should close menu on Escape when focus is on toggle', async () => {
|
|
506
|
+
const toggle = findCustomDropdownToggle();
|
|
507
|
+
const menu = findDropdownMenu();
|
|
508
|
+
|
|
509
|
+
await toggle.trigger('click');
|
|
510
|
+
expect(menu.classes('gl-display-block!')).toBe(true);
|
|
511
|
+
|
|
512
|
+
await toggle.trigger('keydown.esc');
|
|
513
|
+
expect(menu.classes('gl-display-block!')).toBe(false);
|
|
514
|
+
expect(wrapper.emitted(GL_DROPDOWN_HIDDEN)).toHaveLength(1);
|
|
515
|
+
});
|
|
490
516
|
});
|
|
491
517
|
|
|
492
518
|
it('should emit `GL_DROPDOWN_FOCUS_CONTENT` event on `ARROW_DOWN`', () => {
|
|
@@ -400,9 +400,10 @@ export default {
|
|
|
400
400
|
return;
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
+
const hadFocusWithin = this.$el.contains(document.activeElement);
|
|
403
404
|
const hasToggled = await this.toggle(event);
|
|
404
405
|
|
|
405
|
-
if (!
|
|
406
|
+
if (!hadFocusWithin) {
|
|
406
407
|
return;
|
|
407
408
|
}
|
|
408
409
|
|
|
@@ -459,6 +460,7 @@ export default {
|
|
|
459
460
|
ref="toggle"
|
|
460
461
|
data-testid="base-dropdown-toggle"
|
|
461
462
|
v-on="toggleListeners"
|
|
463
|
+
@keydown.esc.stop.prevent="close"
|
|
462
464
|
>
|
|
463
465
|
<!-- @slot Custom toggle button content -->
|
|
464
466
|
<slot name="toggle">
|
|
@@ -59,6 +59,7 @@ describe('GlCollapsibleListbox', () => {
|
|
|
59
59
|
const findSelectAllButton = () => wrapper.find("[data-testid='listbox-select-all-button']");
|
|
60
60
|
const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver);
|
|
61
61
|
const findDropdownMenu = () => wrapper.find("[data-testid='base-dropdown-menu']");
|
|
62
|
+
const findDropdownToggle = () => wrapper.find("[data-testid='base-dropdown-toggle'");
|
|
62
63
|
|
|
63
64
|
it('passes custom offset to the base dropdown', () => {
|
|
64
65
|
const dropdownOffset = { mainAxis: 10, crossAxis: 40 };
|
|
@@ -927,4 +928,17 @@ describe('GlCollapsibleListbox', () => {
|
|
|
927
928
|
expect(findDropdownMenu().classes()).not.toContain('gl-display-block!');
|
|
928
929
|
});
|
|
929
930
|
});
|
|
931
|
+
|
|
932
|
+
it('focuses the toggle when closed by ESC key while item had focus', async () => {
|
|
933
|
+
buildWrapper({
|
|
934
|
+
selected: mockOptions[1].value,
|
|
935
|
+
items: mockOptions,
|
|
936
|
+
startOpened: true,
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
await nextTick();
|
|
940
|
+
findListItem(1).trigger('keydown.esc');
|
|
941
|
+
await nextTick();
|
|
942
|
+
expect(findDropdownToggle().element).toHaveFocus();
|
|
943
|
+
});
|
|
930
944
|
});
|
|
@@ -18,13 +18,12 @@ table.gl-table {
|
|
|
18
18
|
@include gl-font-weight-bold;
|
|
19
19
|
@include gl-text-gray-900;
|
|
20
20
|
|
|
21
|
-
&.gl-text-right >
|
|
22
|
-
@include gl-display-flex;
|
|
21
|
+
&.gl-text-right > div {
|
|
23
22
|
flex-direction: row-reverse;
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
div.gl-ml-2 {
|
|
26
25
|
@include gl-ml-0;
|
|
27
|
-
@include gl-mr-
|
|
26
|
+
@include gl-mr-2;
|
|
28
27
|
}
|
|
29
28
|
}
|
|
30
29
|
|
|
@@ -129,6 +128,14 @@ table.gl-table {
|
|
|
129
128
|
&.table-hover td.table-secondary:hover {
|
|
130
129
|
background-color: $gray-10;
|
|
131
130
|
}
|
|
131
|
+
|
|
132
|
+
thead th:hover {
|
|
133
|
+
background-color: transparent !important;
|
|
134
|
+
|
|
135
|
+
[name='sort-icon'] {
|
|
136
|
+
display: flex !important;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
132
139
|
}
|
|
133
140
|
|
|
134
141
|
.table.b-table > thead > tr > th,
|
|
@@ -27,7 +27,10 @@ describe('GlTable', () => {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
const findBTable = () => wrapper.findComponent(BTable);
|
|
30
|
-
const
|
|
30
|
+
const findColHeaderAt = (index) => findBTable().find('thead').findAll('th').at(index);
|
|
31
|
+
const findFirstColHeader = () => findColHeaderAt(0);
|
|
32
|
+
const findSortIconForHeaderAt = (index) =>
|
|
33
|
+
findColHeaderAt(index).find('[data-testid="sort-icon"]');
|
|
31
34
|
|
|
32
35
|
afterEach(() => {
|
|
33
36
|
logWarning.mockClear();
|
|
@@ -80,19 +83,31 @@ describe('GlTable', () => {
|
|
|
80
83
|
});
|
|
81
84
|
|
|
82
85
|
describe('sortable columns', () => {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
const fields = [
|
|
87
|
+
{
|
|
88
|
+
key: 'name',
|
|
89
|
+
label: 'Name column',
|
|
90
|
+
sortable: true,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
key: 'age',
|
|
94
|
+
label: 'Age column',
|
|
95
|
+
sortable: true,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
key: 'email',
|
|
99
|
+
label: 'Email column',
|
|
100
|
+
sortable: false,
|
|
101
|
+
},
|
|
102
|
+
];
|
|
88
103
|
|
|
89
104
|
describe('without custom slots', () => {
|
|
90
105
|
beforeEach(() => {
|
|
91
|
-
factory({ mountFn: mount, props: { fields
|
|
106
|
+
factory({ mountFn: mount, props: { fields } });
|
|
92
107
|
});
|
|
93
108
|
|
|
94
109
|
it('sets the correct column label', () => {
|
|
95
|
-
expect(findFirstColHeader().text()).toMatch(
|
|
110
|
+
expect(findFirstColHeader().text()).toMatch(fields[0].label);
|
|
96
111
|
});
|
|
97
112
|
|
|
98
113
|
it('renders the ascending sort icon', async () => {
|
|
@@ -112,6 +127,40 @@ describe('GlTable', () => {
|
|
|
112
127
|
|
|
113
128
|
expect(headerText).toContain('↓');
|
|
114
129
|
});
|
|
130
|
+
|
|
131
|
+
it('sets initial sorting column using the sortBy property', () => {
|
|
132
|
+
factory({ mountFn: mount, props: { fields, sortBy: 'age' } });
|
|
133
|
+
|
|
134
|
+
expect(findSortIconForHeaderAt(0).classes()).toContain('gl-display-none');
|
|
135
|
+
expect(findSortIconForHeaderAt(1).classes()).not.toContain('gl-display-none');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('sets initial sorting direction using the sortDesc property', () => {
|
|
139
|
+
factory({
|
|
140
|
+
mountFn: mount,
|
|
141
|
+
props: { fields, sortBy: 'age', sortDesc: true },
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
expect(findColHeaderAt(1).text()).toContain('↓');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('does not render sort icon for non-sortable columns', () => {
|
|
148
|
+
expect(findSortIconForHeaderAt(2).exists()).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('changing the active sort column', () => {
|
|
152
|
+
it('hides sorting icon in previous active sort column', async () => {
|
|
153
|
+
await findColHeaderAt(0).trigger('click');
|
|
154
|
+
|
|
155
|
+
expect(findSortIconForHeaderAt(0).classes()).not.toContain('gl-display-none');
|
|
156
|
+
expect(findSortIconForHeaderAt(1).classes()).toContain('gl-display-none');
|
|
157
|
+
|
|
158
|
+
await findColHeaderAt(1).trigger('click');
|
|
159
|
+
|
|
160
|
+
expect(findSortIconForHeaderAt(0).classes()).toContain('gl-display-none');
|
|
161
|
+
expect(findSortIconForHeaderAt(1).classes()).not.toContain('gl-display-none');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
115
164
|
});
|
|
116
165
|
|
|
117
166
|
describe('when headers are customized via slots', () => {
|
|
@@ -121,7 +170,7 @@ describe('GlTable', () => {
|
|
|
121
170
|
factory({
|
|
122
171
|
mountFn: mount,
|
|
123
172
|
props: {
|
|
124
|
-
fields
|
|
173
|
+
fields,
|
|
125
174
|
},
|
|
126
175
|
scopedSlots: {
|
|
127
176
|
'head(name)': `<div>${customSlotContent}</div>`,
|
|
@@ -73,11 +73,22 @@ export default {
|
|
|
73
73
|
isSortable({ field }) {
|
|
74
74
|
return field?.sortable;
|
|
75
75
|
},
|
|
76
|
+
activeSortingColumn({ field }) {
|
|
77
|
+
return this.localSortBy === field?.key;
|
|
78
|
+
},
|
|
76
79
|
getSortingIcon({ field }) {
|
|
77
|
-
if (this.
|
|
78
|
-
|
|
80
|
+
if (this.activeSortingColumn({ field })) {
|
|
81
|
+
if (this.localSortDesc) {
|
|
82
|
+
return '↓';
|
|
83
|
+
}
|
|
84
|
+
return '↑';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (this.$attrs['sort-direction'] === 'desc') {
|
|
88
|
+
return '↓';
|
|
79
89
|
}
|
|
80
|
-
|
|
90
|
+
|
|
91
|
+
return '↑';
|
|
81
92
|
},
|
|
82
93
|
},
|
|
83
94
|
};
|
|
@@ -96,27 +107,21 @@ export default {
|
|
|
96
107
|
<slot :name="slotName" v-bind="scope"></slot>
|
|
97
108
|
</template>
|
|
98
109
|
<template v-for="headSlotName in headSlots" #[headSlotName]="scope">
|
|
99
|
-
<
|
|
110
|
+
<div :key="headSlotName" class="gl-display-flex">
|
|
100
111
|
<slot :name="headSlotName" v-bind="scope"
|
|
101
112
|
><span>{{ scope.label }}</span></slot
|
|
102
113
|
><template v-if="isSortable(scope)">
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
class="gl-ml-3 gl-min-w-5 gl-text-gray-900 gl-text-center"
|
|
113
|
-
name="sort-icon"
|
|
114
|
-
>
|
|
115
|
-
↓
|
|
116
|
-
</span>
|
|
117
|
-
<span v-else class="gl-display-inline-block gl-w-5 gl-h-3 gl-ml-3"></span>
|
|
114
|
+
<div class="gl-ml-2 gl-w-5 gl-text-gray-900 gl-display-flex gl-justify-content-center">
|
|
115
|
+
<span
|
|
116
|
+
name="sort-icon"
|
|
117
|
+
data-testid="sort-icon"
|
|
118
|
+
:class="{ 'gl-display-none': !activeSortingColumn(scope) }"
|
|
119
|
+
>
|
|
120
|
+
{{ getSortingIcon(scope) }}
|
|
121
|
+
</span>
|
|
122
|
+
</div>
|
|
118
123
|
</template>
|
|
119
|
-
</
|
|
124
|
+
</div>
|
|
120
125
|
</template>
|
|
121
126
|
</b-table>
|
|
122
127
|
</template>
|
|
@@ -39,16 +39,11 @@ const generatePartialSlashCommands = () => {
|
|
|
39
39
|
describe('GlDuoChat', () => {
|
|
40
40
|
let wrapper;
|
|
41
41
|
|
|
42
|
-
const createComponent = ({ propsData = {},
|
|
42
|
+
const createComponent = ({ propsData = {}, slots = {} } = {}) => {
|
|
43
43
|
jest.spyOn(DuoChatLoader.methods, 'computeTransitionWidth').mockImplementation();
|
|
44
44
|
|
|
45
45
|
wrapper = shallowMount(GlDuoChat, {
|
|
46
46
|
propsData,
|
|
47
|
-
data() {
|
|
48
|
-
return {
|
|
49
|
-
...data,
|
|
50
|
-
};
|
|
51
|
-
},
|
|
52
47
|
slots,
|
|
53
48
|
stubs: {
|
|
54
49
|
DuoChatLoader,
|
|
@@ -76,6 +71,8 @@ describe('GlDuoChat', () => {
|
|
|
76
71
|
const findSlashCommands = () => wrapper.findAllComponents(GlDropdownItem);
|
|
77
72
|
const findSelectedSlashCommand = () => wrapper.find('.active-command');
|
|
78
73
|
|
|
74
|
+
const setPromptInput = (val) => findChatInput().vm.$emit('input', val);
|
|
75
|
+
|
|
79
76
|
beforeEach(() => {
|
|
80
77
|
createComponent();
|
|
81
78
|
});
|
|
@@ -321,8 +318,8 @@ describe('GlDuoChat', () => {
|
|
|
321
318
|
`('$event should $action the prompt form', ({ trigger, expectEmitted } = {}) => {
|
|
322
319
|
createComponent({
|
|
323
320
|
propsData: { messages: [], isChatAvailable: true },
|
|
324
|
-
data: { prompt: promptStr },
|
|
325
321
|
});
|
|
322
|
+
setPromptInput(promptStr);
|
|
326
323
|
trigger();
|
|
327
324
|
expect(wrapper.emitted('send-chat-prompt')).toEqual(expectEmitted);
|
|
328
325
|
});
|
|
@@ -335,8 +332,8 @@ describe('GlDuoChat', () => {
|
|
|
335
332
|
`('prevents submission when loading $desc', ({ msgs } = {}) => {
|
|
336
333
|
createComponent({
|
|
337
334
|
propsData: { isChatAvailable: true, isLoading: true, messages: msgs },
|
|
338
|
-
data: { prompt: promptStr },
|
|
339
335
|
});
|
|
336
|
+
setPromptInput(promptStr);
|
|
340
337
|
clickSubmit();
|
|
341
338
|
expect(wrapper.emitted('send-chat-prompt')).toBe(undefined);
|
|
342
339
|
});
|
|
@@ -352,18 +349,21 @@ describe('GlDuoChat', () => {
|
|
|
352
349
|
])('prevents submission when streaming (messages = "%o")', (msgs = []) => {
|
|
353
350
|
createComponent({
|
|
354
351
|
propsData: { isChatAvailable: true, messages: msgs },
|
|
355
|
-
data: { prompt: promptStr },
|
|
356
352
|
});
|
|
353
|
+
setPromptInput(promptStr);
|
|
357
354
|
clickSubmit();
|
|
358
355
|
expect(wrapper.emitted('send-chat-prompt')).toBe(undefined);
|
|
359
356
|
});
|
|
360
357
|
|
|
361
358
|
it('resets the prompt after form submission', async () => {
|
|
362
359
|
const prompt = 'foo';
|
|
363
|
-
createComponent(
|
|
360
|
+
createComponent();
|
|
361
|
+
await setPromptInput(prompt);
|
|
364
362
|
expect(findChatInput().props('value')).toBe(prompt);
|
|
363
|
+
|
|
365
364
|
clickSubmit();
|
|
366
365
|
await nextTick();
|
|
366
|
+
|
|
367
367
|
expect(findChatInput().props('value')).toBe('');
|
|
368
368
|
});
|
|
369
369
|
|
|
@@ -372,7 +372,8 @@ describe('GlDuoChat', () => {
|
|
|
372
372
|
jest.spyOn(HTMLElement.prototype, 'focus').mockImplementation(function focusMockImpl() {
|
|
373
373
|
focusSpy(this);
|
|
374
374
|
});
|
|
375
|
-
createComponent(
|
|
375
|
+
createComponent();
|
|
376
|
+
await setPromptInput('TEST!');
|
|
376
377
|
|
|
377
378
|
clickSubmit();
|
|
378
379
|
await nextTick();
|
|
@@ -385,8 +386,8 @@ describe('GlDuoChat', () => {
|
|
|
385
386
|
it('emits the event with the reset prompt', () => {
|
|
386
387
|
createComponent({
|
|
387
388
|
propsData: { messages, isChatAvailable: true },
|
|
388
|
-
data: { prompt: CHAT_RESET_MESSAGE },
|
|
389
389
|
});
|
|
390
|
+
setPromptInput(CHAT_RESET_MESSAGE);
|
|
390
391
|
clickSubmit();
|
|
391
392
|
|
|
392
393
|
expect(wrapper.emitted('send-chat-prompt')).toEqual([[CHAT_RESET_MESSAGE]]);
|
|
@@ -396,8 +397,8 @@ describe('GlDuoChat', () => {
|
|
|
396
397
|
it('reset does nothing when chat is loading', () => {
|
|
397
398
|
createComponent({
|
|
398
399
|
propsData: { messages, isChatAvailable: true, isLoading: true },
|
|
399
|
-
data: { prompt: CHAT_RESET_MESSAGE },
|
|
400
400
|
});
|
|
401
|
+
setPromptInput(CHAT_RESET_MESSAGE);
|
|
401
402
|
clickSubmit();
|
|
402
403
|
|
|
403
404
|
expect(wrapper.emitted('send-chat-prompt')).toBeUndefined();
|
|
@@ -407,8 +408,8 @@ describe('GlDuoChat', () => {
|
|
|
407
408
|
it('reset does nothing when there are no messages', () => {
|
|
408
409
|
createComponent({
|
|
409
410
|
propsData: { messages: [], isChatAvailable: true },
|
|
410
|
-
data: { prompt: CHAT_RESET_MESSAGE },
|
|
411
411
|
});
|
|
412
|
+
setPromptInput(CHAT_RESET_MESSAGE);
|
|
412
413
|
clickSubmit();
|
|
413
414
|
|
|
414
415
|
expect(wrapper.emitted('send-chat-prompt')).toBeUndefined();
|
|
@@ -429,8 +430,8 @@ describe('GlDuoChat', () => {
|
|
|
429
430
|
messages: existingMessages,
|
|
430
431
|
isChatAvailable: true,
|
|
431
432
|
},
|
|
432
|
-
data: { prompt: CHAT_RESET_MESSAGE },
|
|
433
433
|
});
|
|
434
|
+
setPromptInput(CHAT_RESET_MESSAGE);
|
|
434
435
|
clickSubmit();
|
|
435
436
|
|
|
436
437
|
expect(wrapper.emitted('send-chat-prompt')).toBeUndefined();
|
|
@@ -442,16 +443,17 @@ describe('GlDuoChat', () => {
|
|
|
442
443
|
});
|
|
443
444
|
});
|
|
444
445
|
|
|
445
|
-
describe('
|
|
446
|
-
|
|
446
|
+
describe('when closed', () => {
|
|
447
|
+
beforeEach(async () => {
|
|
447
448
|
findCloseButton().vm.$emit('click');
|
|
448
449
|
await nextTick();
|
|
449
|
-
expect(findChatComponent().exists()).toBe(false);
|
|
450
450
|
});
|
|
451
451
|
|
|
452
|
-
it('
|
|
453
|
-
createComponent({ data: { isHidden: true } });
|
|
452
|
+
it('is hidden', () => {
|
|
454
453
|
expect(findChatComponent().exists()).toBe(false);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('when starts loading, resets hidden status', async () => {
|
|
455
457
|
// setProps is justified here because we are testing the component's
|
|
456
458
|
// reactive behavior which consistutes an exception
|
|
457
459
|
// See https://docs.gitlab.com/ee/development/fe_guide/style/vue.html#setting-component-state
|
|
@@ -461,7 +463,9 @@ describe('GlDuoChat', () => {
|
|
|
461
463
|
await nextTick();
|
|
462
464
|
expect(findChatComponent().exists()).toBe(true);
|
|
463
465
|
});
|
|
466
|
+
});
|
|
464
467
|
|
|
468
|
+
describe('interaction', () => {
|
|
465
469
|
it('renders custom loader when isLoading', () => {
|
|
466
470
|
createComponent({ propsData: { isLoading: true } });
|
|
467
471
|
expect(findCustomLoader().exists()).toBe(true);
|
|
@@ -554,11 +558,8 @@ describe('GlDuoChat', () => {
|
|
|
554
558
|
});
|
|
555
559
|
|
|
556
560
|
it('does not render slash commands when prompt is "/"', async () => {
|
|
557
|
-
createComponent(
|
|
558
|
-
|
|
559
|
-
prompt: '/',
|
|
560
|
-
},
|
|
561
|
-
});
|
|
561
|
+
createComponent();
|
|
562
|
+
setPromptInput('/');
|
|
562
563
|
|
|
563
564
|
await nextTick();
|
|
564
565
|
expect(findSlashCommandsCard().exists()).toBe(false);
|
|
@@ -582,10 +583,8 @@ describe('GlDuoChat', () => {
|
|
|
582
583
|
propsData: {
|
|
583
584
|
slashCommands,
|
|
584
585
|
},
|
|
585
|
-
data: {
|
|
586
|
-
prompt: '/',
|
|
587
|
-
},
|
|
588
586
|
});
|
|
587
|
+
setPromptInput('/');
|
|
589
588
|
|
|
590
589
|
await nextTick();
|
|
591
590
|
expect(findSlashCommandsCard().exists()).toBe(true);
|
|
@@ -615,10 +614,8 @@ describe('GlDuoChat', () => {
|
|
|
615
614
|
propsData: {
|
|
616
615
|
slashCommands,
|
|
617
616
|
},
|
|
618
|
-
data: {
|
|
619
|
-
prompt,
|
|
620
|
-
},
|
|
621
617
|
});
|
|
618
|
+
setPromptInput(prompt);
|
|
622
619
|
|
|
623
620
|
await nextTick();
|
|
624
621
|
expect(findSlashCommandsCard().exists()).toBe(false);
|
|
@@ -634,10 +631,8 @@ describe('GlDuoChat', () => {
|
|
|
634
631
|
propsData: {
|
|
635
632
|
slashCommands,
|
|
636
633
|
},
|
|
637
|
-
data: {
|
|
638
|
-
prompt,
|
|
639
|
-
},
|
|
640
634
|
});
|
|
635
|
+
setPromptInput(prompt);
|
|
641
636
|
|
|
642
637
|
await nextTick();
|
|
643
638
|
expect(findSlashCommandsCard().exists()).toBe(true);
|
|
@@ -653,10 +648,8 @@ describe('GlDuoChat', () => {
|
|
|
653
648
|
propsData: {
|
|
654
649
|
slashCommands,
|
|
655
650
|
},
|
|
656
|
-
data: {
|
|
657
|
-
prompt,
|
|
658
|
-
},
|
|
659
651
|
});
|
|
652
|
+
setPromptInput(prompt);
|
|
660
653
|
|
|
661
654
|
await nextTick();
|
|
662
655
|
expect(findSlashCommandsCard().exists()).toBe(false);
|
|
@@ -688,10 +681,8 @@ describe('GlDuoChat', () => {
|
|
|
688
681
|
propsData: {
|
|
689
682
|
slashCommands,
|
|
690
683
|
},
|
|
691
|
-
data: {
|
|
692
|
-
prompt,
|
|
693
|
-
},
|
|
694
684
|
});
|
|
685
|
+
setPromptInput(prompt);
|
|
695
686
|
|
|
696
687
|
await nextTick();
|
|
697
688
|
expect(findSlashCommands()).toHaveLength(expectedCommands.length);
|
|
@@ -709,10 +700,8 @@ describe('GlDuoChat', () => {
|
|
|
709
700
|
slashCommands,
|
|
710
701
|
messages,
|
|
711
702
|
},
|
|
712
|
-
data: {
|
|
713
|
-
prompt: '/',
|
|
714
|
-
},
|
|
715
703
|
});
|
|
704
|
+
setPromptInput('/');
|
|
716
705
|
});
|
|
717
706
|
|
|
718
707
|
it('toggles through commands on ArrowDown', async () => {
|
|
@@ -777,10 +766,8 @@ describe('GlDuoChat', () => {
|
|
|
777
766
|
slashCommands,
|
|
778
767
|
messages,
|
|
779
768
|
},
|
|
780
|
-
data: {
|
|
781
|
-
prompt: '/',
|
|
782
|
-
},
|
|
783
769
|
});
|
|
770
|
+
setPromptInput('/');
|
|
784
771
|
});
|
|
785
772
|
|
|
786
773
|
it('updates the selected command when hovering over it', async () => {
|