@gitlab/ui 66.23.1 → 66.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/components/experimental/duo/chat/components/duo_chat_loader/duo_chat_loader.js +100 -0
  3. package/dist/components/experimental/duo/chat/constants.js +2 -1
  4. package/dist/components/experimental/duo/user_feedback/user_feedback.js +1 -1
  5. package/dist/components/experimental/duo/user_feedback/user_feedback_modal.js +1 -1
  6. package/dist/index.css +1 -1
  7. package/dist/index.css.map +1 -1
  8. package/dist/tokens/css/tokens.css +1 -1
  9. package/dist/tokens/css/tokens.dark.css +1 -1
  10. package/dist/tokens/js/tokens.dark.js +1 -1
  11. package/dist/tokens/js/tokens.js +1 -1
  12. package/dist/tokens/scss/_tokens.dark.scss +1 -1
  13. package/dist/tokens/scss/_tokens.scss +1 -1
  14. package/package.json +1 -1
  15. package/src/components/experimental/duo/chat/components/duo_chat_loader/duo_chat_loader.md +1 -0
  16. package/src/components/experimental/duo/chat/components/duo_chat_loader/duo_chat_loader.scss +67 -0
  17. package/src/components/experimental/duo/chat/components/duo_chat_loader/duo_chat_loader.spec.js +70 -0
  18. package/src/components/experimental/duo/chat/components/duo_chat_loader/duo_chat_loader.stories.js +31 -0
  19. package/src/components/experimental/duo/chat/components/duo_chat_loader/duo_chat_loader.vue +94 -0
  20. package/src/components/experimental/duo/chat/constants.js +2 -0
  21. package/src/components/experimental/duo/user_feedback/user_feedback.md +52 -6
  22. package/src/components/experimental/duo/user_feedback/user_feedback.spec.js +20 -3
  23. package/src/components/experimental/duo/user_feedback/user_feedback.stories.js +68 -8
  24. package/src/components/experimental/duo/user_feedback/user_feedback.vue +7 -1
  25. package/src/components/experimental/duo/user_feedback/user_feedback_modal.spec.js +18 -1
  26. package/src/components/experimental/duo/user_feedback/user_feedback_modal.vue +10 -6
  27. package/src/scss/components.scss +1 -0
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Wed, 04 Oct 2023 09:48:50 GMT
3
+ * Generated on Thu, 05 Oct 2023 10:55:11 GMT
4
4
  */
5
5
 
6
6
  :root {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Wed, 04 Oct 2023 09:48:50 GMT
3
+ * Generated on Thu, 05 Oct 2023 10:55:11 GMT
4
4
  */
5
5
 
6
6
  :root {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Wed, 04 Oct 2023 09:48:50 GMT
3
+ * Generated on Thu, 05 Oct 2023 10:55:11 GMT
4
4
  */
5
5
 
6
6
  export const BLACK = "#fff";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Wed, 04 Oct 2023 09:48:50 GMT
3
+ * Generated on Thu, 05 Oct 2023 10:55:11 GMT
4
4
  */
5
5
 
6
6
  export const BLACK = "#000";
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Do not edit directly
3
- // Generated on Wed, 04 Oct 2023 09:48:50 GMT
3
+ // Generated on Thu, 05 Oct 2023 10:55:11 GMT
4
4
 
5
5
  $red-950: #fff4f3;
6
6
  $red-900: #fcf1ef;
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Do not edit directly
3
- // Generated on Wed, 04 Oct 2023 09:48:50 GMT
3
+ // Generated on Thu, 05 Oct 2023 10:55:11 GMT
4
4
 
5
5
  $gl-line-height-52: 3.25rem;
6
6
  $gl-line-height-44: 2.75rem;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "66.23.1",
3
+ "version": "66.25.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -0,0 +1 @@
1
+ The Duo Chat loader. Is shown while waiting for the chat response.
@@ -0,0 +1,67 @@
1
+ .duo-chat-loader {
2
+ @include gl-py-3;
3
+ @include gl-px-4;
4
+ @include gl-mb-4;
5
+ @include gl-rounded-lg;
6
+ @include gl-rounded-bottom-left-none;
7
+ @include gl-display-flex;
8
+ @include gl-text-gray-500;
9
+
10
+ .transition {
11
+ transition: width 0.5s ease;
12
+ }
13
+
14
+ .text-enter {
15
+ @include gl-opacity-0;
16
+ }
17
+
18
+ .text-enter-active {
19
+ transition: opacity 1s ease-in; // we intentionally overlap this transition with .text-leave-active by 200ms
20
+ }
21
+
22
+ .text-enter-to {
23
+ @include gl-opacity-10;
24
+ }
25
+
26
+ .text-leave {
27
+ @include gl-opacity-10;
28
+ }
29
+
30
+ .text-leave-active {
31
+ transition: opacity 0.7s linear;
32
+ }
33
+
34
+ .text-leave-to {
35
+ @include gl-opacity-0;
36
+ }
37
+
38
+ &__dot {
39
+ display: inline-block;
40
+ width: 0.3rem;
41
+ height: 0.3rem;
42
+ background-color: $gray-200;
43
+ border-radius: 100%;
44
+ animation: DuoChatLoading 1400ms ease-in-out infinite;
45
+ animation-fill-mode: both;
46
+ }
47
+
48
+ &__dot--1 {
49
+ animation-delay: -0.3s;
50
+ }
51
+
52
+ &__dot--2 {
53
+ animation-delay: -0.15s;
54
+ }
55
+ }
56
+
57
+ @keyframes DuoChatLoading {
58
+ 0%,
59
+ 80%,
60
+ 100% {
61
+ transform: scale(0);
62
+ }
63
+
64
+ 40% {
65
+ transform: scale(1);
66
+ }
67
+ }
@@ -0,0 +1,70 @@
1
+ import { nextTick } from 'vue';
2
+ import { mount } from '@vue/test-utils';
3
+ import { LOADING_TRANSITION_DURATION } from '../../constants';
4
+ import GlDuoChatLoader from './duo_chat_loader.vue';
5
+
6
+ jest.useFakeTimers();
7
+
8
+ describe('GlDuoChatLoader', () => {
9
+ let wrapper;
10
+
11
+ const createComponent = ({ propsData = {} } = {}) => {
12
+ wrapper = mount(GlDuoChatLoader, {
13
+ propsData,
14
+ });
15
+ };
16
+
17
+ const transition = async () => {
18
+ jest.advanceTimersByTime(LOADING_TRANSITION_DURATION);
19
+ await nextTick();
20
+ };
21
+
22
+ const findTransitionText = () => wrapper.find('[data-testid="current-transition"]').text();
23
+ const findToolText = () => wrapper.find('[data-testid="tool"]');
24
+
25
+ describe('rendering', () => {
26
+ const defaultTool = GlDuoChatLoader.props.toolName.default;
27
+
28
+ it('displays a default tool', async () => {
29
+ createComponent();
30
+ await nextTick();
31
+
32
+ expect(findToolText().text()).toBe(defaultTool);
33
+ });
34
+
35
+ it('shows the `toolName` when it is passed', async () => {
36
+ createComponent({
37
+ propsData: {
38
+ toolName: 'foo',
39
+ },
40
+ });
41
+ await nextTick();
42
+
43
+ expect(findToolText().text()).toBe('foo');
44
+ expect(findToolText().text()).not.toBe(defaultTool);
45
+ });
46
+
47
+ it('cycles through transition texts', async () => {
48
+ createComponent();
49
+ await nextTick();
50
+
51
+ expect(findTransitionText()).toEqual('finding');
52
+
53
+ await transition();
54
+
55
+ expect(findTransitionText()).toEqual('working on');
56
+
57
+ await transition();
58
+
59
+ expect(findTransitionText()).toEqual('generating');
60
+
61
+ await transition();
62
+
63
+ expect(findTransitionText()).toEqual('producing');
64
+
65
+ await transition();
66
+
67
+ expect(findTransitionText()).toEqual('finding');
68
+ });
69
+ });
70
+ });
@@ -0,0 +1,31 @@
1
+ import GlDuoChatLoader from './duo_chat_loader.vue';
2
+ import readme from './duo_chat_loader.md';
3
+
4
+ const defaultValue = (prop) => GlDuoChatLoader.props[prop].default;
5
+
6
+ const generateProps = ({ toolName = defaultValue('toolName') } = {}) => ({
7
+ toolName,
8
+ });
9
+
10
+ const Template = (args, { argTypes }) => ({
11
+ components: { GlDuoChatLoader },
12
+ props: Object.keys(argTypes),
13
+ template: `
14
+ <gl-duo-chat-loader :tool-name="toolName" />
15
+ `,
16
+ });
17
+
18
+ export const Default = Template.bind({});
19
+ Default.args = generateProps();
20
+
21
+ export default {
22
+ title: 'experimental/duo/chat/duo_chat_loader',
23
+ component: GlDuoChatLoader,
24
+ parameters: {
25
+ docs: {
26
+ description: {
27
+ component: readme,
28
+ },
29
+ },
30
+ },
31
+ };
@@ -0,0 +1,94 @@
1
+ <script>
2
+ import GlSprintf from '../../../../../utilities/sprintf/sprintf.vue';
3
+ import { LOADING_TRANSITION_DURATION } from '../../constants';
4
+
5
+ export const i18n = {
6
+ LOADER_LOADING_MESSAGE: '%{tool} is %{transition} an answer',
7
+ LOADER_LOADING_TRANSITIONS: ['finding', 'working on', 'generating', 'producing'],
8
+ GITLAB_DUO: 'GitLab Duo',
9
+ };
10
+
11
+ export default {
12
+ name: 'GlDuoChatLoader',
13
+ components: {
14
+ GlSprintf,
15
+ },
16
+ i18n,
17
+ props: {
18
+ /**
19
+ * The message containing the name of the current AI tool working on the answer.
20
+ */
21
+ toolName: {
22
+ type: String,
23
+ required: false,
24
+ default: i18n.GITLAB_DUO,
25
+ },
26
+ },
27
+ data() {
28
+ return {
29
+ loadingSequence: 0,
30
+ timeout: null,
31
+ };
32
+ },
33
+ beforeDestroy() {
34
+ clearTimeout(this.timeout);
35
+ },
36
+ mounted() {
37
+ this.computeTransitionWidth();
38
+ this.enter();
39
+ },
40
+ methods: {
41
+ computeTransitionWidth() {
42
+ const container = this.$refs.transition;
43
+ const active = this.$refs.currentTransition[0]; // There's only one `currentTransition` ref at a time, but refs in v-for loops are always Arrays
44
+ const { width, height } = active.getBoundingClientRect();
45
+ container.$el.style.width = `${width}px`;
46
+ container.$el.style.height = `${height}px`;
47
+ },
48
+ enter() {
49
+ clearTimeout(this.timeout);
50
+ this.timeout = setTimeout(() => {
51
+ this.loadingSequence =
52
+ (this.loadingSequence + 1) % this.$options.i18n.LOADER_LOADING_TRANSITIONS.length;
53
+ this.enter();
54
+ }, LOADING_TRANSITION_DURATION);
55
+ },
56
+ isCurrentTransition(index) {
57
+ return index === this.loadingSequence;
58
+ },
59
+ },
60
+ };
61
+ </script>
62
+
63
+ <template>
64
+ <div class="duo-chat-loader">
65
+ <div class="gl-display-flex gl-align-items-center gl-mr-3">
66
+ <div class="duo-chat-loader__dot duo-chat-loader__dot--1"></div>
67
+ <div class="duo-chat-loader__dot duo-chat-loader__dot--2"></div>
68
+ <div class="duo-chat-loader__dot duo-chat-loader__dot--3"></div>
69
+ </div>
70
+ <gl-sprintf :message="$options.i18n.LOADER_LOADING_MESSAGE">
71
+ <template #tool>
72
+ <strong class="gl-mr-2" data-testid="tool">{{ toolName }}</strong>
73
+ </template>
74
+ <template #transition>
75
+ <transition-group
76
+ ref="transition"
77
+ name="text"
78
+ class="transition gl-display-inline-block gl-mx-2"
79
+ @after-leave="computeTransitionWidth"
80
+ >
81
+ <span
82
+ v-for="(message, index) in $options.i18n.LOADER_LOADING_TRANSITIONS"
83
+ v-show="isCurrentTransition(index)"
84
+ :ref="isCurrentTransition(index) ? 'currentTransition' : ''"
85
+ :key="message"
86
+ :data-testid="isCurrentTransition(index) ? 'current-transition' : ''"
87
+ class="gl-white-space-nowrap"
88
+ >{{ message }}</span
89
+ >
90
+ </transition-group>
91
+ </template>
92
+ </gl-sprintf>
93
+ </div>
94
+ </template>
@@ -1,3 +1,5 @@
1
+ export const LOADING_TRANSITION_DURATION = 7500;
2
+
1
3
  export const DOCUMENTATION_SOURCE_TYPES = {
2
4
  HANDBOOK: {
3
5
  value: 'handbook',
@@ -5,7 +5,7 @@ the form data on submission.
5
5
 
6
6
  ## Custom button text
7
7
 
8
- The component allows to customize the button text, presented to the user.
8
+ The component allows to customize the button text presented to the user.
9
9
 
10
10
  ```html
11
11
  <gl-duo-user-feedback feedback-link-text="Leave your custom feedback" />
@@ -13,9 +13,9 @@ The component allows to customize the button text, presented to the user.
13
13
 
14
14
  ## Linking to a separate feedback form
15
15
 
16
- The main goal of this component is to provide the advanced feedback form. However, it might not
17
- be necessary for all consumers. In such a case, the component allows to bypass the default
18
- form and link to an external feedback page/form.
16
+ The main goal of this component is to provide an advanced feedback form. However, it might only
17
+ be necessary for some consumers. In such a case, the component bypasses the default
18
+ form and links to an external feedback page/form.
19
19
 
20
20
  ```html
21
21
  <gl-duo-user-feedback feedback-link-url="https://gitlab.com" />
@@ -29,8 +29,8 @@ This component emits the `feedback` event with all the options selected in the f
29
29
  <gl-duo-user-feedback @feedback="myEventTracker" />
30
30
  ```
31
31
 
32
- The returned event contains two props (`feedbackChoices` and `extendedTextFeedback`) coming from
33
- the underlying `FeedbackModal` component. Here's the example of a possible event:
32
+ The returned event contains two props (`feedbackChoices` and `extendedTextFeedback`) from
33
+ the underlying `FeedbackModal` component. Here's an example of a possible event:
34
34
 
35
35
  ```json
36
36
  {
@@ -38,3 +38,49 @@ the underlying `FeedbackModal` component. Here's the example of a possible event
38
38
  "extendedTextFeedback": "The answer was too long to understand"
39
39
  }
40
40
  ```
41
+
42
+ ## Using the `feedback-extra-fields` slot
43
+
44
+ By default, the component renders one extra textarea field to gather additional feedback
45
+ information from users. However, it may not be enough sometimes, and different use cases of
46
+ this component might need to fine-tune the form to gather information most suitable for that
47
+ or another use case. For this purpose, the component provides the `feedback-extra-fields`
48
+ slot, which can override the default textarea with different fields/information.
49
+
50
+ Note, however, that the content put into this slot will override the default textarea. So,
51
+ if you want to append additional fields, instead of completely overriding the default textarea,
52
+ you must copy the textarea field from the `DuoChatFeedbackModal` component into the slot.
53
+
54
+ ```html
55
+ <gl-duo-user-feedback
56
+ :feedback-link-text="feedbackLinkText"
57
+ :feedback-link-url="feedbackLinkUrl"
58
+ @feedback="logEvent"
59
+ >
60
+ <template #feedback-extra-fields>
61
+ <div class="gl-mb-5">
62
+ <gl-alert variant="info" :dismissible="false">
63
+ GitLab team members can not see your conversation. Please be as descriptive as possible.
64
+ </gl-alert>
65
+ </div>
66
+ <gl-form-group label="What were you doing?" optional>
67
+ <gl-form-textarea
68
+ placeholder="The situation in which you interacted with GitLab Duo Chat."
69
+ v-model="didWhat"
70
+ />
71
+ </gl-form-group>
72
+ <gl-form-group label="What were you expecting from the response?" optional>
73
+ <gl-form-textarea
74
+ placeholder="What kind of information or assistance were you hoping to receive?"
75
+ v-model="expectedWhat"
76
+ />
77
+ </gl-form-group>
78
+ <gl-form-group label="How could the response be improved?" optional>
79
+ <gl-form-textarea
80
+ placeholder="How the response might better meet your needs."
81
+ v-model="improveWhat"
82
+ />
83
+ </gl-form-group>
84
+ </template>
85
+ </gl-duo-user-feedback>
86
+ ```
@@ -1,14 +1,18 @@
1
1
  import { nextTick } from 'vue';
2
2
  import { shallowMount } from '@vue/test-utils';
3
3
  import { GlButton } from '../../../../index';
4
- import DuoChatFeedbackModal from './user_feedback_modal.vue';
4
+ import FeedbackModal from './user_feedback_modal.vue';
5
5
  import UserFeedback, { i18n } from './user_feedback.vue';
6
6
 
7
+ const DummyComponent = {
8
+ template: '<p>dummy</p>',
9
+ };
10
+
7
11
  describe('UserFeedback', () => {
8
12
  let wrapper;
9
13
  const eventName = 'test_event_name';
10
14
 
11
- const createComponent = ({ props, data = {} } = {}) => {
15
+ const createComponent = ({ props, data = {}, slots = {} } = {}) => {
12
16
  wrapper = shallowMount(UserFeedback, {
13
17
  data() {
14
18
  return data;
@@ -17,11 +21,15 @@ describe('UserFeedback', () => {
17
21
  eventName,
18
22
  ...props,
19
23
  },
24
+ stubs: {
25
+ FeedbackModal,
26
+ },
27
+ slots,
20
28
  });
21
29
  };
22
30
 
23
31
  const findButton = () => wrapper.findComponent(GlButton);
24
- const findModal = () => wrapper.findComponent(DuoChatFeedbackModal);
32
+ const findModal = () => wrapper.findComponent(FeedbackModal);
25
33
 
26
34
  beforeEach(() => {
27
35
  createComponent();
@@ -77,4 +85,13 @@ describe('UserFeedback', () => {
77
85
  expect(findModal().exists()).toBe(false);
78
86
  });
79
87
  });
88
+
89
+ describe('slots', () => {
90
+ it('renders the `feedback-extra-fields` slot', () => {
91
+ expect(wrapper.findComponent(DummyComponent).exists()).toBe(false);
92
+ wrapper.destroy();
93
+ createComponent({ slots: { 'feedback-extra-fields': DummyComponent } });
94
+ expect(wrapper.findComponent(DummyComponent).exists()).toBe(true);
95
+ });
96
+ });
80
97
  });
@@ -1,4 +1,5 @@
1
- import GlUserFeedback from './user_feedback.vue';
1
+ import { GlAlert, GlFormGroup, GlFormTextarea } from '../../../../index';
2
+ import GlDuoUserFeedback from './user_feedback.vue';
2
3
  import readme from './user_feedback.md';
3
4
 
4
5
  const generateProps = ({ feedbackLinkText, feedbackLinkUrl } = {}) => ({
@@ -6,8 +7,13 @@ const generateProps = ({ feedbackLinkText, feedbackLinkUrl } = {}) => ({
6
7
  feedbackLinkUrl,
7
8
  });
8
9
 
9
- const Template = (args, { argTypes }) => ({
10
- components: { GlUserFeedback },
10
+ export const Default = (args, { argTypes }) => ({
11
+ components: {
12
+ GlDuoUserFeedback,
13
+ GlAlert,
14
+ GlFormGroup,
15
+ GlFormTextarea,
16
+ },
11
17
  props: Object.keys(argTypes),
12
18
  data() {
13
19
  return {
@@ -21,7 +27,7 @@ const Template = (args, { argTypes }) => ({
21
27
  },
22
28
  template: `
23
29
  <div>
24
- <gl-user-feedback
30
+ <gl-duo-user-feedback
25
31
  :feedback-link-text="feedbackLinkText"
26
32
  :feedback-link-url="feedbackLinkUrl"
27
33
  @feedback="logEvent"/>
@@ -29,13 +35,67 @@ const Template = (args, { argTypes }) => ({
29
35
  </div>
30
36
  `,
31
37
  });
32
-
33
- export const Default = Template.bind({});
34
38
  Default.args = generateProps();
35
39
 
40
+ export const Slots = (args, { argTypes }) => ({
41
+ components: {
42
+ GlDuoUserFeedback,
43
+ GlAlert,
44
+ GlFormGroup,
45
+ GlFormTextarea,
46
+ },
47
+ props: Object.keys(argTypes),
48
+ data() {
49
+ return {
50
+ eventOutput: '',
51
+ didWhat: '',
52
+ expectedWhat: '',
53
+ improveWhat: '',
54
+ };
55
+ },
56
+ methods: {
57
+ logEvent(event) {
58
+ const { feedbackChoices } = event;
59
+ this.eventOutput = JSON.stringify({
60
+ feedbackChoices,
61
+ didWhat: this.didWhat,
62
+ expectedWhat: this.expectedWhat,
63
+ improveWhat: this.improveWhat,
64
+ });
65
+ },
66
+ },
67
+ template: `
68
+ <div>
69
+ <gl-duo-user-feedback
70
+ :feedback-link-text="feedbackLinkText"
71
+ :feedback-link-url="feedbackLinkUrl"
72
+ @feedback="logEvent">
73
+ <template #feedback-extra-fields>
74
+ <div class="gl-mb-5">
75
+ <gl-alert variant="info" :dismissible="false">
76
+ GitLab team members can not see your conversation. Please be as descriptive as possible.
77
+ </gl-alert>
78
+ </div>
79
+ <gl-form-group label="What were you doing?" optional>
80
+ <gl-form-textarea placeholder="The situation in which you interacted with GitLab Duo Chat." v-model="didWhat" />
81
+ </gl-form-group>
82
+ <gl-form-group label="What were you expecting from the response?" optional>
83
+ <gl-form-textarea placeholder="What kind of information or assistance were you hoping to receive?" v-model="expectedWhat" />
84
+ </gl-form-group>
85
+ <gl-form-group label="How could the response be improved?" optional>
86
+ <gl-form-textarea placeholder="How the response might better meet your needs."v-model="improveWhat" />
87
+ </gl-form-group>
88
+ </template>
89
+ </gl-duo-user-feedback>
90
+ <p v-if="eventOutput"><code>{{ eventOutput }}</code></p>
91
+ </div>
92
+ `,
93
+ });
94
+ Slots.parameters = { controls: { disable: true } };
95
+
36
96
  export default {
37
- title: 'experimental/duo/user-feedback',
38
- component: GlUserFeedback,
97
+ title: 'experimental/duo/duo-user-feedback',
98
+ component: GlDuoUserFeedback,
39
99
  parameters: {
40
100
  storyshots: { disable: true },
41
101
  docs: {
@@ -64,6 +64,7 @@ export default {
64
64
  variant="link"
65
65
  target="_blank"
66
66
  :href="feedbackLinkUrl"
67
+ button-text-classes="gl-white-space-normal! gl-text-left"
67
68
  @click="shouldRenderModal && $refs.feedbackModal.show()"
68
69
  >{{ feedbackLinkText }}</gl-button
69
70
  >
@@ -71,6 +72,11 @@ export default {
71
72
  {{ $options.i18n.FEEDBACK_THANKS }}
72
73
  </span>
73
74
  </div>
74
- <feedback-modal v-if="shouldRenderModal" ref="feedbackModal" @feedback-submitted="notify" />
75
+ <feedback-modal v-if="shouldRenderModal" ref="feedbackModal" @feedback-submitted="notify">
76
+ <template #feedback-extra-fields>
77
+ <!-- @slot The addition Feedback form fields. -->
78
+ <slot name="feedback-extra-fields"></slot>
79
+ </template>
80
+ </feedback-modal>
75
81
  </div>
76
82
  </template>
@@ -2,6 +2,10 @@ import { shallowMount } from '@vue/test-utils';
2
2
  import { GlModal, GlFormCheckboxGroup, GlFormCheckbox, GlFormTextarea } from '../../../../index';
3
3
  import FeedbackModal, { feedbackOptions } from './user_feedback_modal.vue';
4
4
 
5
+ const DummyComponent = {
6
+ template: '<p>dummy</p>',
7
+ };
8
+
5
9
  describe('FeedbackModal', () => {
6
10
  let wrapper;
7
11
 
@@ -15,8 +19,9 @@ describe('FeedbackModal', () => {
15
19
  .at(index)
16
20
  .vm.$emit('input', [feedbackOptions[index].value]);
17
21
  };
18
- const createComponent = () => {
22
+ const createComponent = (options = {}) => {
19
23
  wrapper = shallowMount(FeedbackModal, {
24
+ ...options,
20
25
  stubs: {
21
26
  GlModal,
22
27
  GlFormCheckboxGroup,
@@ -58,4 +63,16 @@ describe('FeedbackModal', () => {
58
63
  expect(wrapper.emitted('feedback-submitted')).toBeUndefined();
59
64
  });
60
65
  });
66
+
67
+ describe('slots', () => {
68
+ it('renders the `feedback-extra-fields` slot with default content', () => {
69
+ expect(wrapper.findComponent(DummyComponent).exists()).toBe(false);
70
+ expect(findTextarea().exists()).toBe(true);
71
+ wrapper.destroy();
72
+
73
+ createComponent({ slots: { 'feedback-extra-fields': DummyComponent } });
74
+ expect(wrapper.findComponent(DummyComponent).exists()).toBe(true);
75
+ expect(findTextarea().exists()).toBe(false);
76
+ });
77
+ });
61
78
  });
@@ -114,11 +114,15 @@ export default {
114
114
  :options="$options.feedbackOptions"
115
115
  />
116
116
  </gl-form-group>
117
- <gl-form-group :label="$options.i18n.MODAL_MORE_LABEL" optional>
118
- <gl-form-textarea
119
- v-model="extendedFeedback"
120
- :placeholder="$options.i18n.MODAL_MORE_PLACEHOLDER"
121
- />
122
- </gl-form-group>
117
+
118
+ <!-- @slot The addition Feedback form fields. -->
119
+ <slot name="feedback-extra-fields">
120
+ <gl-form-group :label="$options.i18n.MODAL_MORE_LABEL" optional>
121
+ <gl-form-textarea
122
+ v-model="extendedFeedback"
123
+ :placeholder="$options.i18n.MODAL_MORE_PLACEHOLDER"
124
+ />
125
+ </gl-form-group>
126
+ </slot>
123
127
  </gl-modal>
124
128
  </template>
@@ -3,6 +3,7 @@
3
3
  // @import '../components/base/dropdown/dropdown'
4
4
  //
5
5
  // ADD COMPONENT IMPORTS - needed for yarn generate:component. Do not remove
6
+ @import '../components/experimental/duo/chat/components/duo_chat_loader/duo_chat_loader';
6
7
  @import '../components/base/new_dropdowns/disclosure/disclosure_dropdown';
7
8
  @import '../components/base/keyset_pagination/keyset_pagination';
8
9
  @import '../components/charts/gauge/gauge';