@gitlab/ui 79.1.0 → 79.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/dist/components/base/form/form_textarea/form_textarea.js +6 -3
- package/dist/components/base/new_dropdowns/base_dropdown/base_dropdown.js +9 -6
- package/dist/components/base/new_dropdowns/constants.js +2 -2
- package/dist/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.js +17 -8
- 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/dist/utils/utils.js +19 -1
- package/package.json +1 -1
- package/src/components/base/form/form_textarea/form_textarea.spec.js +37 -0
- package/src/components/base/form/form_textarea/form_textarea.vue +6 -3
- package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.spec.js +31 -26
- package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.vue +16 -6
- package/src/components/base/new_dropdowns/constants.js +1 -1
- package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.scss +15 -1
- package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.spec.js +51 -45
- package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.stories.js +8 -0
- package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.vue +52 -28
- package/src/utils/utils.js +18 -0
- package/src/utils/utils.spec.js +52 -0
package/dist/tokens/js/tokens.js
CHANGED
package/dist/utils/utils.js
CHANGED
|
@@ -194,4 +194,22 @@ function filterVisible(els) {
|
|
|
194
194
|
return (els || []).filter(el => isVisible(el));
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
|
|
197
|
+
/**
|
|
198
|
+
* Given an element, returns a Rect object
|
|
199
|
+
* with top and bottom boundaries removed.
|
|
200
|
+
*/
|
|
201
|
+
function getHorizontalBoundingClientRect(el) {
|
|
202
|
+
const rect = el === null || el === void 0 ? void 0 : el.getBoundingClientRect();
|
|
203
|
+
if (rect) {
|
|
204
|
+
return {
|
|
205
|
+
x: rect.x,
|
|
206
|
+
width: rect.width,
|
|
207
|
+
y: 0,
|
|
208
|
+
// top of the document
|
|
209
|
+
height: document.documentElement.clientHeight // bottom of the document
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export { colorFromBackground, debounceByAnimationFrame, filterVisible, focusFirstFocusableElement, getColorContrast, getHorizontalBoundingClientRect, hexToRgba, isDev, isElementFocusable, isElementTabbable, logWarning, relativeLuminance, rgbFromHex, rgbFromString, stopEvent, throttle, toSrgb, uid };
|
package/package.json
CHANGED
|
@@ -201,5 +201,42 @@ describe('GlFormTextArea', () => {
|
|
|
201
201
|
|
|
202
202
|
itUpdatesDebouncedScreenReaderText(expectedText);
|
|
203
203
|
});
|
|
204
|
+
|
|
205
|
+
describe('when `value` prop is `null`', () => {
|
|
206
|
+
const expectedText = `${characterCount} characters remaining`;
|
|
207
|
+
|
|
208
|
+
beforeEach(() => {
|
|
209
|
+
createComponent({
|
|
210
|
+
value: null,
|
|
211
|
+
characterCount,
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('displays remaining characters', () => {
|
|
216
|
+
expect(wrapper.text()).toContain(expectedText);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
itUpdatesDebouncedScreenReaderText(expectedText);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('when `value` prop is updated to `null`', () => {
|
|
223
|
+
const textareaCharacterCount = 5;
|
|
224
|
+
const expectedText = `${characterCount} characters remaining`;
|
|
225
|
+
|
|
226
|
+
beforeEach(() => {
|
|
227
|
+
createComponent({
|
|
228
|
+
value: 'a'.repeat(textareaCharacterCount),
|
|
229
|
+
characterCount,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
wrapper.setProps({ value: null });
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('updates character count text', () => {
|
|
236
|
+
expect(wrapper.text()).toContain(expectedText);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
itUpdatesDebouncedScreenReaderText(expectedText);
|
|
240
|
+
});
|
|
204
241
|
});
|
|
205
242
|
});
|
|
@@ -98,7 +98,7 @@ export default {
|
|
|
98
98
|
return;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
this.remainingCharacterCount = this.characterCount - newValue
|
|
101
|
+
this.remainingCharacterCount = this.characterCount - this.valueLength(newValue);
|
|
102
102
|
this.debouncedUpdateRemainingCharacterCountSrOnly(newValue);
|
|
103
103
|
},
|
|
104
104
|
},
|
|
@@ -111,16 +111,19 @@ export default {
|
|
|
111
111
|
);
|
|
112
112
|
},
|
|
113
113
|
methods: {
|
|
114
|
+
valueLength(value) {
|
|
115
|
+
return value?.length || 0;
|
|
116
|
+
},
|
|
114
117
|
handleKeyPress(e) {
|
|
115
118
|
if (e.keyCode === 13 && (e.metaKey || e.ctrlKey)) {
|
|
116
119
|
this.$emit('submit');
|
|
117
120
|
}
|
|
118
121
|
},
|
|
119
122
|
updateRemainingCharacterCountSrOnly(newValue) {
|
|
120
|
-
this.remainingCharacterCountSrOnly = this.characterCount - newValue
|
|
123
|
+
this.remainingCharacterCountSrOnly = this.characterCount - this.valueLength(newValue);
|
|
121
124
|
},
|
|
122
125
|
initialRemainingCharacterCount() {
|
|
123
|
-
return this.characterCount - this.value
|
|
126
|
+
return this.characterCount - this.valueLength(this.value);
|
|
124
127
|
},
|
|
125
128
|
},
|
|
126
129
|
};
|
|
@@ -116,15 +116,16 @@ describe('base dropdown', () => {
|
|
|
116
116
|
strategy: 'absolute',
|
|
117
117
|
middleware: [
|
|
118
118
|
offset({ mainAxis: DEFAULT_OFFSET }),
|
|
119
|
-
autoPlacement(
|
|
120
|
-
alignment: 'start',
|
|
121
|
-
boundary: document.querySelector('main'),
|
|
122
|
-
allowedPlacements: ['bottom-start', 'top-start', 'bottom-end', 'top-end'],
|
|
123
|
-
}),
|
|
119
|
+
autoPlacement(expect.any(Function)),
|
|
124
120
|
shift(),
|
|
125
121
|
],
|
|
126
122
|
}
|
|
127
123
|
);
|
|
124
|
+
expect(autoPlacement.mock.calls[0][0]()).toEqual({
|
|
125
|
+
alignment: 'start',
|
|
126
|
+
boundary: { x: 0, y: 0, width: 0, height: 0 },
|
|
127
|
+
allowedPlacements: ['bottom-start', 'top-start', 'bottom-end', 'top-end'],
|
|
128
|
+
});
|
|
128
129
|
|
|
129
130
|
document.body.innerHTML = '';
|
|
130
131
|
});
|
|
@@ -141,15 +142,16 @@ describe('base dropdown', () => {
|
|
|
141
142
|
strategy: 'absolute',
|
|
142
143
|
middleware: [
|
|
143
144
|
offset({ mainAxis: DEFAULT_OFFSET }),
|
|
144
|
-
autoPlacement(
|
|
145
|
-
alignment: 'start',
|
|
146
|
-
boundary: 'clippingAncestors',
|
|
147
|
-
allowedPlacements: ['bottom-start', 'top-start', 'bottom-end', 'top-end'],
|
|
148
|
-
}),
|
|
145
|
+
autoPlacement(expect.any(Function)),
|
|
149
146
|
shift(),
|
|
150
147
|
],
|
|
151
148
|
}
|
|
152
149
|
);
|
|
150
|
+
expect(autoPlacement.mock.calls[0][0]()).toEqual({
|
|
151
|
+
alignment: 'start',
|
|
152
|
+
boundary: 'clippingAncestors',
|
|
153
|
+
allowedPlacements: ['bottom-start', 'top-start', 'bottom-end', 'top-end'],
|
|
154
|
+
});
|
|
153
155
|
});
|
|
154
156
|
|
|
155
157
|
it('initializes Floating UI with reference and floating elements and config for center-aligned menu', async () => {
|
|
@@ -164,15 +166,16 @@ describe('base dropdown', () => {
|
|
|
164
166
|
strategy: 'absolute',
|
|
165
167
|
middleware: [
|
|
166
168
|
offset({ mainAxis: DEFAULT_OFFSET }),
|
|
167
|
-
autoPlacement(
|
|
168
|
-
alignment: undefined,
|
|
169
|
-
boundary: 'clippingAncestors',
|
|
170
|
-
allowedPlacements: ['bottom', 'top'],
|
|
171
|
-
}),
|
|
169
|
+
autoPlacement(expect.any(Function)),
|
|
172
170
|
shift(),
|
|
173
171
|
],
|
|
174
172
|
}
|
|
175
173
|
);
|
|
174
|
+
expect(autoPlacement.mock.calls[0][0]()).toEqual({
|
|
175
|
+
alignment: undefined,
|
|
176
|
+
boundary: 'clippingAncestors',
|
|
177
|
+
allowedPlacements: ['bottom', 'top'],
|
|
178
|
+
});
|
|
176
179
|
});
|
|
177
180
|
|
|
178
181
|
it('initializes Floating UI with reference and floating elements and config for right-aligned menu', async () => {
|
|
@@ -187,15 +190,16 @@ describe('base dropdown', () => {
|
|
|
187
190
|
strategy: 'absolute',
|
|
188
191
|
middleware: [
|
|
189
192
|
offset({ mainAxis: DEFAULT_OFFSET }),
|
|
190
|
-
autoPlacement(
|
|
191
|
-
alignment: 'end',
|
|
192
|
-
boundary: 'clippingAncestors',
|
|
193
|
-
allowedPlacements: ['bottom-start', 'top-start', 'bottom-end', 'top-end'],
|
|
194
|
-
}),
|
|
193
|
+
autoPlacement(expect.any(Function)),
|
|
195
194
|
shift(),
|
|
196
195
|
],
|
|
197
196
|
}
|
|
198
197
|
);
|
|
198
|
+
expect(autoPlacement.mock.calls[0][0]()).toEqual({
|
|
199
|
+
alignment: 'end',
|
|
200
|
+
boundary: 'clippingAncestors',
|
|
201
|
+
allowedPlacements: ['bottom-start', 'top-start', 'bottom-end', 'top-end'],
|
|
202
|
+
});
|
|
199
203
|
});
|
|
200
204
|
|
|
201
205
|
it('initializes Floating UI with reference and floating elements and config for `right-start` aligned menu', async () => {
|
|
@@ -210,15 +214,16 @@ describe('base dropdown', () => {
|
|
|
210
214
|
strategy: 'absolute',
|
|
211
215
|
middleware: [
|
|
212
216
|
offset({ mainAxis: DEFAULT_OFFSET }),
|
|
213
|
-
autoPlacement(
|
|
214
|
-
alignment: 'start',
|
|
215
|
-
boundary: 'clippingAncestors',
|
|
216
|
-
allowedPlacements: ['right-start', 'right-end', 'left-start', 'left-end'],
|
|
217
|
-
}),
|
|
217
|
+
autoPlacement(expect.any(Function)),
|
|
218
218
|
shift(),
|
|
219
219
|
],
|
|
220
220
|
}
|
|
221
221
|
);
|
|
222
|
+
expect(autoPlacement.mock.calls[0][0]()).toEqual({
|
|
223
|
+
alignment: 'start',
|
|
224
|
+
boundary: 'clippingAncestors',
|
|
225
|
+
allowedPlacements: ['right-start', 'right-end', 'left-start', 'left-end'],
|
|
226
|
+
});
|
|
222
227
|
});
|
|
223
228
|
|
|
224
229
|
it("passes custom offset to Floating UI's middleware", async () => {
|
|
@@ -235,7 +240,7 @@ describe('base dropdown', () => {
|
|
|
235
240
|
{
|
|
236
241
|
placement: 'bottom-end',
|
|
237
242
|
strategy: 'absolute',
|
|
238
|
-
middleware: [offset(customOffset), autoPlacement(expect.any(
|
|
243
|
+
middleware: [offset(customOffset), autoPlacement(expect.any(Function)), shift()],
|
|
239
244
|
}
|
|
240
245
|
);
|
|
241
246
|
});
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
dropdownVariantOptions,
|
|
10
10
|
} from '../../../../utils/constants';
|
|
11
11
|
import {
|
|
12
|
-
|
|
12
|
+
GL_DROPDOWN_HORIZONTAL_BOUNDARY_SELECTOR,
|
|
13
13
|
GL_DROPDOWN_SHOWN,
|
|
14
14
|
GL_DROPDOWN_HIDDEN,
|
|
15
15
|
GL_DROPDOWN_BEFORE_CLOSE,
|
|
@@ -21,7 +21,12 @@ import {
|
|
|
21
21
|
POSITION_ABSOLUTE,
|
|
22
22
|
POSITION_FIXED,
|
|
23
23
|
} from '../constants';
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
logWarning,
|
|
26
|
+
isElementTabbable,
|
|
27
|
+
isElementFocusable,
|
|
28
|
+
getHorizontalBoundingClientRect,
|
|
29
|
+
} from '../../../../utils/utils';
|
|
25
30
|
|
|
26
31
|
import GlButton from '../../button/button.vue';
|
|
27
32
|
import GlIcon from '../../icon/icon.vue';
|
|
@@ -271,10 +276,15 @@ export default {
|
|
|
271
276
|
strategy: this.positioningStrategy,
|
|
272
277
|
middleware: [
|
|
273
278
|
offset(this.offset),
|
|
274
|
-
autoPlacement({
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
279
|
+
autoPlacement(() => {
|
|
280
|
+
const autoHorizontalBoundary = getHorizontalBoundingClientRect(
|
|
281
|
+
this.$el.closest(GL_DROPDOWN_HORIZONTAL_BOUNDARY_SELECTOR)
|
|
282
|
+
);
|
|
283
|
+
return {
|
|
284
|
+
alignment,
|
|
285
|
+
boundary: autoHorizontalBoundary || 'clippingAncestors',
|
|
286
|
+
allowedPlacements: dropdownAllowedAutoPlacements[this.placement],
|
|
287
|
+
};
|
|
278
288
|
}),
|
|
279
289
|
shift(),
|
|
280
290
|
size({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// base dropdown events
|
|
2
|
-
export const
|
|
2
|
+
export const GL_DROPDOWN_HORIZONTAL_BOUNDARY_SELECTOR = 'main';
|
|
3
3
|
export const GL_DROPDOWN_SHOWN = 'shown';
|
|
4
4
|
export const GL_DROPDOWN_HIDDEN = 'hidden';
|
|
5
5
|
export const GL_DROPDOWN_BEFORE_CLOSE = 'beforeClose';
|
package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.scss
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
.duo-chat-message {
|
|
2
2
|
max-width: 90%;
|
|
3
|
+
position: relative;
|
|
3
4
|
|
|
4
5
|
code {
|
|
5
6
|
@include gl-bg-gray-100;
|
|
6
7
|
}
|
|
7
8
|
|
|
9
|
+
pre.code {
|
|
10
|
+
@include gl-bg-white;
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
pre code {
|
|
9
14
|
@include gl-font-sm;
|
|
10
15
|
@include gl-line-height-1;
|
|
@@ -30,4 +35,13 @@
|
|
|
30
35
|
@include gl-opacity-10;
|
|
31
36
|
}
|
|
32
37
|
}
|
|
33
|
-
|
|
38
|
+
|
|
39
|
+
.has-error {
|
|
40
|
+
margin-left: $gl-spacing-scale-6;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.error-icon {
|
|
44
|
+
top: 14px;
|
|
45
|
+
position: absolute;
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.spec.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { nextTick } from 'vue';
|
|
2
2
|
import { shallowMount } from '@vue/test-utils';
|
|
3
3
|
import GlDuoUserFeedback from '../../../user_feedback/user_feedback.vue';
|
|
4
|
+
import GlIcon from '../../../../../base/icon/icon.vue';
|
|
4
5
|
import {
|
|
5
6
|
MOCK_USER_PROMPT_MESSAGE,
|
|
6
7
|
MOCK_RESPONSE_MESSAGE,
|
|
@@ -12,10 +13,13 @@ import GlDuoChatMessage from './duo_chat_message.vue';
|
|
|
12
13
|
describe('DuoChatMessage', () => {
|
|
13
14
|
let wrapper;
|
|
14
15
|
|
|
16
|
+
const findContentWrapper = () => wrapper.findComponent({ ref: 'content-wrapper' });
|
|
15
17
|
const findContent = () => wrapper.findComponent({ ref: 'content' });
|
|
18
|
+
const findErrorMessage = () => wrapper.findComponent({ ref: 'error-message' });
|
|
16
19
|
const findDocumentSources = () => wrapper.findComponent(DocumentationSources);
|
|
17
20
|
const findUserFeedback = () => wrapper.findComponent(GlDuoUserFeedback);
|
|
18
21
|
const findCopyCodeButton = () => wrapper.find('copy-code');
|
|
22
|
+
const findErrorIcon = () => wrapper.findComponent(GlIcon);
|
|
19
23
|
const mockMarkdownContent = 'foo **bar**';
|
|
20
24
|
|
|
21
25
|
let renderMarkdown;
|
|
@@ -82,6 +86,10 @@ describe('DuoChatMessage', () => {
|
|
|
82
86
|
it('does not render the user feedback component', () => {
|
|
83
87
|
expect(findUserFeedback().exists()).toBe(false);
|
|
84
88
|
});
|
|
89
|
+
|
|
90
|
+
it('does not render the error icon', () => {
|
|
91
|
+
expect(findErrorIcon().exists()).toBe(false);
|
|
92
|
+
});
|
|
85
93
|
});
|
|
86
94
|
|
|
87
95
|
describe('rendering with assistant message', () => {
|
|
@@ -100,6 +108,10 @@ describe('DuoChatMessage', () => {
|
|
|
100
108
|
expect(findDocumentSources().props('sources')).toEqual(MOCK_RESPONSE_MESSAGE.extras.sources);
|
|
101
109
|
});
|
|
102
110
|
|
|
111
|
+
it('does not render the error icon', () => {
|
|
112
|
+
expect(findErrorIcon().exists()).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
|
|
103
115
|
it.each([null, undefined, ''])(
|
|
104
116
|
'does not render sources component when `sources` is %s',
|
|
105
117
|
(sources) => {
|
|
@@ -139,7 +151,19 @@ describe('DuoChatMessage', () => {
|
|
|
139
151
|
});
|
|
140
152
|
|
|
141
153
|
describe('message output', () => {
|
|
142
|
-
it('
|
|
154
|
+
it('renders the warning icon when message has errors', () => {
|
|
155
|
+
createComponent({
|
|
156
|
+
message: {
|
|
157
|
+
...MOCK_USER_PROMPT_MESSAGE,
|
|
158
|
+
errors: ['foo'],
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
expect(findErrorIcon().exists()).toBe(true);
|
|
162
|
+
expect(findErrorMessage().text()).toBe('foo');
|
|
163
|
+
expect(findContentWrapper().classes()).toContain('has-error');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('outputs errors as icon if they are present', async () => {
|
|
143
167
|
const errors = ['error1', 'error2', 'error3'];
|
|
144
168
|
|
|
145
169
|
createComponent({
|
|
@@ -147,15 +171,30 @@ describe('DuoChatMessage', () => {
|
|
|
147
171
|
...MOCK_USER_PROMPT_MESSAGE,
|
|
148
172
|
errors,
|
|
149
173
|
contentHtml: 'fooHtml barHtml',
|
|
174
|
+
content: 'foo bar',
|
|
175
|
+
chunks: ['a', 'b', 'c'],
|
|
150
176
|
},
|
|
151
177
|
});
|
|
152
178
|
|
|
153
179
|
await nextTick();
|
|
154
180
|
|
|
155
|
-
const
|
|
156
|
-
expect(
|
|
157
|
-
expect(
|
|
158
|
-
expect(
|
|
181
|
+
const errorMessage = findErrorMessage().text();
|
|
182
|
+
expect(errorMessage).toContain(errors[0]);
|
|
183
|
+
expect(errorMessage).toContain(errors[1]);
|
|
184
|
+
expect(errorMessage).toContain(errors[2]);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('outputs errors if message has no content', async () => {
|
|
188
|
+
createComponent({
|
|
189
|
+
message: {
|
|
190
|
+
...MOCK_USER_PROMPT_MESSAGE,
|
|
191
|
+
contentHtml: '',
|
|
192
|
+
content: '',
|
|
193
|
+
errors: ['error'],
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
await nextTick();
|
|
197
|
+
expect(findErrorMessage().text()).toBe('error');
|
|
159
198
|
});
|
|
160
199
|
|
|
161
200
|
it('outputs contentHtml if it is present', async () => {
|
|
@@ -216,23 +255,6 @@ describe('DuoChatMessage', () => {
|
|
|
216
255
|
expect(renderGFM).toHaveBeenCalled();
|
|
217
256
|
});
|
|
218
257
|
|
|
219
|
-
it('sanitizes html produced by errors', async () => {
|
|
220
|
-
createComponent({
|
|
221
|
-
options: {
|
|
222
|
-
provide: null,
|
|
223
|
-
},
|
|
224
|
-
message: {
|
|
225
|
-
...MOCK_USER_PROMPT_MESSAGE,
|
|
226
|
-
errors: ['[click here](javascript:prompt(1))'],
|
|
227
|
-
contentHtml: undefined,
|
|
228
|
-
content: '',
|
|
229
|
-
},
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
await nextTick();
|
|
233
|
-
expect(findContent().html()).toContain('<p><a>click here</a></p>');
|
|
234
|
-
});
|
|
235
|
-
|
|
236
258
|
it('sanitizes html produced by content', async () => {
|
|
237
259
|
createComponent({
|
|
238
260
|
options: {
|
|
@@ -339,27 +361,11 @@ describe('DuoChatMessage', () => {
|
|
|
339
361
|
errors: ['error'],
|
|
340
362
|
},
|
|
341
363
|
});
|
|
342
|
-
|
|
343
|
-
expect(findContent().
|
|
344
|
-
expect(findContent().text()).toContain('error');
|
|
345
|
-
});
|
|
364
|
+
await nextTick();
|
|
365
|
+
expect(findContent().exists()).toBe(false);
|
|
346
366
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
// setProps is justified here because we are testing the component's
|
|
350
|
-
// reactive behavior which consistutes an exception
|
|
351
|
-
// See https://docs.gitlab.com/ee/development/fe_guide/style/vue.html#setting-component-state
|
|
352
|
-
await wrapper.setProps({
|
|
353
|
-
message: {
|
|
354
|
-
...MOCK_USER_PROMPT_MESSAGE,
|
|
355
|
-
contentHtml: '',
|
|
356
|
-
content: '',
|
|
357
|
-
errors,
|
|
358
|
-
},
|
|
359
|
-
});
|
|
360
|
-
expect(findContent().text()).toContain(errors[0]);
|
|
361
|
-
expect(findContent().text()).toContain(errors[1]);
|
|
362
|
-
expect(findContent().text()).toContain(errors[2]);
|
|
367
|
+
expect(findErrorMessage().text()).toBe('error');
|
|
368
|
+
expect(findErrorIcon().exists()).toBe(true);
|
|
363
369
|
});
|
|
364
370
|
|
|
365
371
|
it('hydrates the output message with GLFM if its not a chunk', async () => {
|
|
@@ -479,7 +485,7 @@ describe('DuoChatMessage', () => {
|
|
|
479
485
|
|
|
480
486
|
// setProps is justified here because we are testing the component's
|
|
481
487
|
// reactive behavior which consistutes an exception
|
|
482
|
-
// See https://docs.gitlab.com/ee/
|
|
488
|
+
// See https://docs.gitlab.com/ee/devoutputs errors if message has no contentelopment/fe_guide/style/vue.html#setting-component-state
|
|
483
489
|
await wrapper.setProps({
|
|
484
490
|
message: CHUNK2,
|
|
485
491
|
});
|
|
@@ -519,8 +525,8 @@ describe('DuoChatMessage', () => {
|
|
|
519
525
|
errors,
|
|
520
526
|
},
|
|
521
527
|
});
|
|
522
|
-
|
|
523
|
-
expect(
|
|
528
|
+
|
|
529
|
+
expect(findContentWrapper().text()).toBe(expectedContent);
|
|
524
530
|
}
|
|
525
531
|
);
|
|
526
532
|
});
|
package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.stories.js
CHANGED
|
@@ -33,6 +33,14 @@ Response.args = generateProps({
|
|
|
33
33
|
message: MOCK_RESPONSE_MESSAGE,
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
+
export const ErrorResponse = Template.bind({});
|
|
37
|
+
ErrorResponse.args = generateProps({
|
|
38
|
+
message: {
|
|
39
|
+
...MOCK_RESPONSE_MESSAGE,
|
|
40
|
+
errors: ['Error: Whatever you see is wrong'],
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
36
44
|
export default {
|
|
37
45
|
title: 'experimental/duo/chat/duo-chat-message',
|
|
38
46
|
component: GlDuoChatMessage,
|