@webitel/ui-sdk 3.1.59 → 3.1.61

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 (21) hide show
  1. package/dist/ui-sdk.common.js +15 -15
  2. package/dist/ui-sdk.common.js.map +1 -1
  3. package/dist/ui-sdk.css +1 -1
  4. package/dist/ui-sdk.umd.js +15 -15
  5. package/dist/ui-sdk.umd.js.map +1 -1
  6. package/dist/ui-sdk.umd.min.js +1 -1
  7. package/dist/ui-sdk.umd.min.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/components/molecules/wt-input/wt-input.vue +1 -1
  10. package/src/mixins/validationMixin/validationMixin.js +12 -12
  11. package/src/modules/AuditForm/components/audit-form-question-read-wrapper.vue +3 -3
  12. package/src/modules/AuditForm/components/audit-form-question-write-wrapper.vue +6 -5
  13. package/src/modules/AuditForm/components/audit-form-question.vue +25 -16
  14. package/src/modules/AuditForm/components/audit-form.vue +13 -3
  15. package/src/modules/AuditForm/components/questions/options/__tests__/audit-form-question-options-write-row.spec.js +24 -0
  16. package/src/modules/AuditForm/components/questions/{__tests__ → options/__tests__}/audit-form-question-options.spec.js +2 -3
  17. package/src/modules/AuditForm/components/questions/options/audit-form-question-options-write-row.vue +83 -0
  18. package/src/modules/AuditForm/components/questions/{audit-form-question-options.vue → options/audit-form-question-options.vue} +13 -40
  19. package/src/modules/AuditForm/components/questions/{audit-form-question-score.vue → score/audit-form-question-score.vue} +40 -4
  20. package/src/modules/AuditForm/schemas/AuditFormQuestionSchema.js +3 -3
  21. /package/src/modules/AuditForm/components/questions/{__tests__ → score/__tests__}/audit-form-question-score.spec.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webitel/ui-sdk",
3
- "version": "3.1.59",
3
+ "version": "3.1.61",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve-lib": "vue-cli-service serve",
@@ -170,7 +170,7 @@ export default {
170
170
  default: false,
171
171
  },
172
172
  },
173
- emits: ['update:modelValue', 'input'],
173
+ emits: ['update:modelValue', 'input', 'keyup'],
174
174
  data: () => ({
175
175
  inputType: '',
176
176
  isPasswordVisible: false,
@@ -19,21 +19,21 @@ export default {
19
19
  validationText() {
20
20
  let validationText = '';
21
21
  if (this.isValidation && this.invalid) {
22
- if (this.v.required.$invalid) validationText = this.$t('validation.required');
23
- else if (this.v.numeric.$invalid) validationText = this.$t('validation.numeric');
24
- else if (this.v.email.$invalid) validationText = this.$t('validation.email');
25
- else if (this.v.gatewayHostValidator.$invalid) validationText = this.$t('validation.gatewayHostValidator');
26
- else if (this.v.ipValidator.$invalid) validationText = this.$t('validation.ipValidator');
27
- else if (this.v.macValidator.$invalid) validationText = this.$t('validation.macValidator');
28
- else if (this.v.minValue.$invalid) validationText = `${this.$t('validation.minValue')} ${this.v.$params.minValue.min}`;
29
- else if (this.v.maxValue.$invalid) validationText = `${this.$t('validation.maxValue')} ${this.v.$params.maxValue.max}`;
30
- else if (this.v.sipAccountValidator.$invalid) validationText = this.$t('validation.sipAccountValidator');
31
- else if (this.v.minLength.$invalid) validationText = `${this.$t('validation.minLength')} ${this.v.$params.minLength.min}`;
32
- else if (this.v.url.$invalid) validationText = `${this.$t('validation.url')}`;
22
+ if (this.v.required?.$invalid) validationText = this.$t('validation.required');
23
+ else if (this.v.numeric?.$invalid) validationText = this.$t('validation.numeric');
24
+ else if (this.v.email?.$invalid) validationText = this.$t('validation.email');
25
+ else if (this.v.gatewayHostValidator?.$invalid) validationText = this.$t('validation.gatewayHostValidator');
26
+ else if (this.v.ipValidator?.$invalid) validationText = this.$t('validation.ipValidator');
27
+ else if (this.v.macValidator?.$invalid) validationText = this.$t('validation.macValidator');
28
+ else if (this.v.minValue?.$invalid) validationText = `${this.$t('validation.minValue')} ${this.v.minValue.$params.min}`;
29
+ else if (this.v.maxValue?.$invalid) validationText = `${this.$t('validation.maxValue')} ${this.v.maxValue.$params.max}`;
30
+ else if (this.v.sipAccountValidator?.$invalid) validationText = this.$t('validation.sipAccountValidator');
31
+ else if (this.v.minLength?.$invalid) validationText = `${this.$t('validation.minLength')} ${this.v.minLength.$params.min}`;
32
+ else if (this.v.url?.$invalid) validationText = `${this.$t('validation.url')}`;
33
33
  }
34
34
  // eslint-disable-next-line no-restricted-syntax
35
35
  for (const { name, text } of this.customValidators) {
36
- if (this.v[name].$invalid) validationText = text;
36
+ if (this.v[name]?.$invalid) validationText = text;
37
37
  }
38
38
  return validationText;
39
39
  },
@@ -37,8 +37,8 @@
37
37
  <script setup>
38
38
  import { computed } from 'vue';
39
39
  import { EngineAuditQuestionType } from 'webitel-sdk';
40
- import AuditFormQuestionOptions from './questions/audit-form-question-options.vue';
41
- import AuditFormQuestionScore from './questions/audit-form-question-score.vue';
40
+ import AuditFormQuestionOptions from './questions/options/audit-form-question-options.vue';
41
+ import AuditFormQuestionScore from './questions/score/audit-form-question-score.vue';
42
42
  import WtIcon from '../../../components/atoms/wt-icon/wt-icon.vue';
43
43
 
44
44
  const props = defineProps({
@@ -47,7 +47,7 @@ const props = defineProps({
47
47
  required: true,
48
48
  },
49
49
  result: {
50
- type: Object,
50
+ type: [Object, null],
51
51
  required: true,
52
52
  },
53
53
  disableDragging: {
@@ -4,6 +4,7 @@
4
4
  <wt-switcher
5
5
  :value="question.required"
6
6
  :label="$t('reusable.required')"
7
+ :disabled="first"
7
8
  @change="updateQuestion({ path: 'required', value: $event })"
8
9
  ></wt-switcher>
9
10
  <div class="audit-form-question-write-header__actions">
@@ -20,7 +21,7 @@
20
21
  <template v-slot:activator>
21
22
  <wt-icon-btn
22
23
  icon="bucket"
23
- :disabled="disableDelete"
24
+ :disabled="first"
24
25
  @click="emit('delete')"
25
26
  ></wt-icon-btn>
26
27
  </template>
@@ -68,8 +69,8 @@ import WtIconBtn from '../../../components/molecules/wt-icon-btn/wt-icon-btn.vue
68
69
  import WtInput from '../../../components/molecules/wt-input/wt-input.vue';
69
70
  import WtSelect from '../../../components/molecules/wt-select/wt-select.vue';
70
71
 
71
- import AuditFormQuestionOptions from './questions/audit-form-question-options.vue';
72
- import AuditFormQuestionScore from './questions/audit-form-question-score.vue';
72
+ import AuditFormQuestionOptions from './questions/options/audit-form-question-options.vue';
73
+ import AuditFormQuestionScore from './questions/score/audit-form-question-score.vue';
73
74
 
74
75
  import { generateQuestionScoreSchema } from '../schemas/AuditFormQuestionScoreSchema';
75
76
  import { generateQuestionOptionsSchema } from '../schemas/AuditFormQuestionOptionsSchema';
@@ -79,9 +80,9 @@ const props = defineProps({
79
80
  type: Object,
80
81
  required: true,
81
82
  },
82
- disableDelete: {
83
+ first: {
83
84
  type: Boolean,
84
- default: true,
85
+ default: false,
85
86
  },
86
87
  v: {
87
88
  type: Object,
@@ -1,20 +1,20 @@
1
1
  <template>
2
2
  <component
3
- class="audit-form-question"
3
+ :is="component"
4
+ v-clickaway="saveQuestion"
4
5
  :class="[
5
6
  `audit-form-question--mode-${mode}`
6
7
  ]"
7
- v-clickaway="saveQuestion"
8
- :is="component"
8
+ :disable-dragging="mode === 'fill'"
9
+ :first="first"
9
10
  :question="question"
11
+ :readonly="readonly"
10
12
  :result="result"
11
13
  :v="v$"
12
- :disable-dragging="mode === 'fill'"
13
- :disable-delete="disableDelete"
14
- :readonly="readonly"
14
+ class="audit-form-question"
15
+ @activate="activateQuestion"
15
16
  @copy="emits('copy')"
16
17
  @delete="emits('delete')"
17
- @activate="activateQuestion"
18
18
  @change:question="emits('update:question', $event)"
19
19
  @change:result="emits('update:result', $event)"
20
20
  ></component>
@@ -23,10 +23,10 @@
23
23
  <script setup>
24
24
  import { useVuelidate } from '@vuelidate/core';
25
25
  import { required } from '@vuelidate/validators';
26
- import { computed, ref, toRefs } from 'vue';
26
+ import { computed, onMounted, ref, toRefs } from 'vue';
27
27
  import vClickaway from '../../../directives/clickaway/clickaway';
28
- import QuestionWrite from './audit-form-question-write-wrapper.vue';
29
28
  import QuestionRead from './audit-form-question-read-wrapper.vue';
29
+ import QuestionWrite from './audit-form-question-write-wrapper.vue';
30
30
 
31
31
  const props = defineProps({
32
32
  question: {
@@ -39,9 +39,9 @@ const props = defineProps({
39
39
  mode: {
40
40
  type: String,
41
41
  },
42
- disableDelete: {
42
+ first: {
43
43
  type: Boolean,
44
- default: true,
44
+ default: false,
45
45
  },
46
46
  readonly: {
47
47
  type: Boolean,
@@ -63,17 +63,20 @@ const QuestionState = {
63
63
 
64
64
  const state = ref(QuestionState.SAVED);
65
65
 
66
- const { question } = toRefs(props);
66
+ // is needed for useVuelidate, because props.question/props.result isn't reactive
67
+ const { question, result } = toRefs(props);
67
68
 
68
- // validate only "create" mode
69
69
  const v$ = useVuelidate(computed(() => (
70
70
  (props.mode === 'create')
71
71
  ? {
72
72
  question: {
73
73
  question: { required },
74
74
  },
75
- $autoDirty: true,
76
- } : {})), { question });
75
+ } : {
76
+ result: {
77
+ required: (value) => (question.value.required ? !!value : true),
78
+ },
79
+ })), { question, result }, { $autoDirty: true });
77
80
 
78
81
  const component = computed(() => {
79
82
  if (props.readonly) return QuestionRead;
@@ -92,13 +95,19 @@ function activateQuestion() {
92
95
  if (props.mode !== 'create') return;
93
96
  state.value = QuestionState.EDIT;
94
97
  }
98
+
99
+ // initialize validations
100
+ onMounted(() => {
101
+ v$.value.$touch();
102
+ activateQuestion();
103
+ });
95
104
  </script>
96
105
 
97
106
  <style lang="scss" scoped>
98
107
  .audit-form-question {
99
108
  padding: var(--spacing-sm);
100
- background: var(--main-color);
101
109
  border-radius: var(--border-radius);
110
+ background: var(--main-color);
102
111
  box-shadow: var(--elevation-1);
103
112
 
104
113
  &--mode-create.audit-form-question-read {
@@ -11,7 +11,7 @@
11
11
  :question="question"
12
12
  :result="(result && result[key]) ? result[key] : null"
13
13
  :mode="mode"
14
- :disable-delete="questions.length <= 1"
14
+ :first="key === 0"
15
15
  :readonly="readonly"
16
16
  @copy="copyQuestion({ question, key })"
17
17
  @delete="deleteQuestion({ question, key})"
@@ -32,7 +32,7 @@
32
32
  <script setup>
33
33
  import cloneDeep from 'lodash/cloneDeep';
34
34
  import {
35
- watch, watchEffect, ref, computed,
35
+ watch, watchEffect, ref, computed, onMounted,
36
36
  } from 'vue';
37
37
  import { useVuelidate } from '@vuelidate/core';
38
38
  import { useDestroyableSortable } from '../composables/useDestroyableSortable';
@@ -115,6 +115,12 @@ function initResult() {
115
115
  emit('update:result', result);
116
116
  }
117
117
 
118
+ function initQuestions() {
119
+ if (props.mode === 'create' && !props.questions.length) {
120
+ addQuestion({ question: generateQuestionSchema({ required: true }) });
121
+ }
122
+ }
123
+
118
124
  const sortableWrapper = ref(null);
119
125
 
120
126
  const { reloadSortable } = useDestroyableSortable(sortableWrapper, {
@@ -126,8 +132,12 @@ const { reloadSortable } = useDestroyableSortable(sortableWrapper, {
126
132
  },
127
133
  });
128
134
 
129
- watch(v$, () => emit('update:validation', v$));
135
+ watch(v$, () => emit('update:validation', { invalid: isInvalidForm, v$ }));
130
136
  watchEffect(initResult);
137
+
138
+ onMounted(() => {
139
+ initQuestions();
140
+ });
131
141
  </script>
132
142
 
133
143
  <style lang="scss" scoped>
@@ -0,0 +1,24 @@
1
+ import { shallowMount, mount } from '@vue/test-utils';
2
+ import AuditFormQuestionOptionsWriteRow from '../audit-form-question-options-write-row.vue';
3
+
4
+ describe('AuditFormQuestionOptionsWriteRow', () => {
5
+ it('renders a component', () => {
6
+ const wrapper = shallowMount(AuditFormQuestionOptionsWriteRow, {
7
+ props: {
8
+ option: {},
9
+ },
10
+ });
11
+ expect(wrapper.isVisible()).toBe(true);
12
+ });
13
+ it('emits "delete" event at "delete" icon-btn click', () => {
14
+ const wrapper = mount(AuditFormQuestionOptionsWriteRow, {
15
+ props: {
16
+ option: {},
17
+ },
18
+ });
19
+ const deleteBtn = wrapper.findComponent({ name: 'wt-icon-btn' });
20
+ expect(deleteBtn.props().icon).toBe('bucket');
21
+ deleteBtn.vm.$emit('click');
22
+ expect(wrapper.emitted().delete).toBeTruthy();
23
+ });
24
+ });
@@ -27,9 +27,8 @@ describe('AuditFormQuestionOptions', () => {
27
27
  mode: 'write',
28
28
  },
29
29
  });
30
- const deleteBtn = wrapper.findComponent({ name: 'wt-icon-btn' });
31
- expect(deleteBtn.props().icon).toBe('bucket');
32
- deleteBtn.vm.$emit('click');
30
+ const writeRow = wrapper.findComponent({ name: 'audit-form-question-options-write-row' });
31
+ writeRow.vm.$emit('delete');
33
32
  expect(wrapper.emitted()['change:question'][0][0].options.length).toBe(0);
34
33
  });
35
34
  it('emits result change with selected radio option score', () => {
@@ -0,0 +1,83 @@
1
+ <template>
2
+ <div class="audit-form-question-options-write-row">
3
+ <wt-input
4
+ :label="$t('webitelUI.auditForm.option', 1)"
5
+ :value="option.name"
6
+ :v="v$.option.name"
7
+ @input="emit('update:option', { name: $event, score: option.score })"
8
+ ></wt-input>
9
+ <wt-input
10
+ :label="$t('webitelUI.auditForm.score', 1)"
11
+ :value="option.score"
12
+ :v="v$.option.score"
13
+ type="number"
14
+ @input="emit('update:option', { name: option.name, score: $event })"
15
+ ></wt-input>
16
+ <wt-tooltip>
17
+ <template v-slot:activator>
18
+ <wt-icon-btn
19
+ :disabled="first"
20
+ icon="bucket"
21
+ @click="emit('delete')"
22
+ ></wt-icon-btn>
23
+ </template>
24
+ {{ $t('reusable.delete') }}
25
+ </wt-tooltip>
26
+ </div>
27
+ </template>
28
+
29
+ <script setup>
30
+ import { useVuelidate } from '@vuelidate/core';
31
+ import { required, minValue, maxValue } from '@vuelidate/validators';
32
+ import { computed, onMounted, toRefs } from 'vue';
33
+ import WtTooltip from '../../../../../components/atoms/wt-tooltip/wt-tooltip.vue';
34
+ import WtIconBtn from '../../../../../components/molecules/wt-icon-btn/wt-icon-btn.vue';
35
+ import WtInput from '../../../../../components/molecules/wt-input/wt-input.vue';
36
+
37
+ const props = defineProps({
38
+ option: {
39
+ type: Object,
40
+ required: true,
41
+ },
42
+ first: {
43
+ type: Boolean,
44
+ default: false,
45
+ },
46
+ });
47
+
48
+ const emit = defineEmits([
49
+ 'change:option',
50
+ 'delete',
51
+ ]);
52
+
53
+ // is needed for useVuelidate, because props.question/props.result isn't reactive
54
+ const { option } = toRefs(props);
55
+
56
+ const v$ = useVuelidate(
57
+ computed(() => (
58
+ {
59
+ option: {
60
+ name: { required },
61
+ score: {
62
+ required,
63
+ minValue: minValue(0),
64
+ maxValue: maxValue(10),
65
+ },
66
+ },
67
+ })),
68
+ { option },
69
+ { $autoDirty: true },
70
+ );
71
+
72
+ // init validation
73
+ onMounted(() => v$.value.$touch());
74
+ </script>
75
+
76
+ <style lang="scss" scoped>
77
+ .audit-form-question-options-write-row {
78
+ display: grid;
79
+ align-items: center;
80
+ grid-template-columns: 3fr 1fr 24px;
81
+ gap: var(--spacing-sm);
82
+ }
83
+ </style>
@@ -4,32 +4,14 @@
4
4
  v-if="mode === 'write'"
5
5
  class="audit-form-question-options-write"
6
6
  >
7
- <div
8
- v-for="({ name, score }, key) of question.options"
7
+ <options-write-row
8
+ v-for="(option, key) of question.options"
9
9
  :key="key"
10
- class="audit-form-question-options-write-row"
11
- >
12
- <wt-input
13
- :value="name"
14
- :label="$t('webitelUI.auditForm.option', 1)"
15
- @input="updateQuestion({ path: `options[${key}].name`, value: $event })"
16
- ></wt-input>
17
- <wt-input
18
- :value="score"
19
- :label="$t('webitelUI.auditForm.score', 1)"
20
- type="number"
21
- @input="updateQuestion({ path: `options[${key}].score`, value: $event })"
22
- ></wt-input>
23
- <wt-tooltip>
24
- <template v-slot:activator>
25
- <wt-icon-btn
26
- icon="bucket"
27
- @click="deleteQuestionOption({ key })"
28
- ></wt-icon-btn>
29
- </template>
30
- {{ $t('reusable.delete') }}
31
- </wt-tooltip>
32
- </div>
10
+ :option="option"
11
+ :first="key === 0"
12
+ @update:option="updateQuestion({ path: `options[${key}]`, value: $event })"
13
+ @delete="deleteQuestionOption({ key })"
14
+ ></options-write-row>
33
15
  <wt-button
34
16
  class="audit-form-question-options-write__add-button"
35
17
  @click="addQuestionOption"
@@ -44,8 +26,8 @@
44
26
  v-for="({ name, score }) of question.options"
45
27
  :key="score"
46
28
  :label="name"
47
- :value="score"
48
29
  :selected="result ? result.score : result"
30
+ :value="score"
49
31
  @input="emit('change:result', { score })"
50
32
  ></wt-radio>
51
33
  </div>
@@ -54,13 +36,11 @@
54
36
  </template>
55
37
 
56
38
  <script setup>
57
- import updateObject from '../../../../scripts/updateObject';
58
- import WtTooltip from '../../../../components/atoms/wt-tooltip/wt-tooltip.vue';
59
- import WtIconBtn from '../../../../components/molecules/wt-icon-btn/wt-icon-btn.vue';
60
- import WtInput from '../../../../components/molecules/wt-input/wt-input.vue';
61
- import WtButton from '../../../../components/atoms/wt-button/wt-button.vue';
62
- import WtRadio from '../../../../components/molecules/wt-radio/wt-radio.vue';
63
- import { generateOption } from '../../schemas/AuditFormQuestionOptionsSchema';
39
+ import OptionsWriteRow from './audit-form-question-options-write-row.vue';
40
+ import WtButton from '../../../../../components/atoms/wt-button/wt-button.vue';
41
+ import WtRadio from '../../../../../components/molecules/wt-radio/wt-radio.vue';
42
+ import updateObject from '../../../../../scripts/updateObject';
43
+ import { generateOption } from '../../../schemas/AuditFormQuestionOptionsSchema';
64
44
 
65
45
  const props = defineProps({
66
46
  question: {
@@ -109,13 +89,6 @@ function deleteQuestionOption({ key }) {
109
89
  }
110
90
  }
111
91
 
112
- .audit-form-question-options-write-row {
113
- display: grid;
114
- grid-template-columns: 3fr 1fr 24px;
115
- gap: var(--spacing-sm);
116
- align-items: center;
117
- }
118
-
119
92
  .audit-form-question-options-read {
120
93
  display: flex;
121
94
  flex-direction: column;
@@ -6,12 +6,20 @@
6
6
  >
7
7
  <wt-input
8
8
  :value="question.min"
9
+ :v="v$.question.min"
10
+ :number-min="0"
11
+ :number-max="19"
9
12
  type="number"
13
+ required
10
14
  @input="updateQuestion({ path: 'min', value: $event })"
11
15
  ></wt-input>
12
16
  <wt-input
13
17
  :value="question.max"
18
+ :v="v$.question.max"
19
+ :number-min="1"
20
+ :number-max="20"
14
21
  type="number"
22
+ required
15
23
  @input="updateQuestion({ path: 'max', value: $event })"
16
24
  ></wt-input>
17
25
  </div>
@@ -32,10 +40,12 @@
32
40
  </template>
33
41
 
34
42
  <script setup>
35
- import { computed } from 'vue';
36
- import updateObject from '../../../../scripts/updateObject';
37
- import WtRadio from '../../../../components/molecules/wt-radio/wt-radio.vue';
38
- import WtInput from '../../../../components/molecules/wt-input/wt-input.vue';
43
+ import { useVuelidate } from '@vuelidate/core';
44
+ import { maxValue, minValue, required } from '@vuelidate/validators';
45
+ import { computed, onMounted, toRefs } from 'vue';
46
+ import updateObject from '../../../../../scripts/updateObject';
47
+ import WtRadio from '../../../../../components/molecules/wt-radio/wt-radio.vue';
48
+ import WtInput from '../../../../../components/molecules/wt-input/wt-input.vue';
39
49
 
40
50
  const props = defineProps({
41
51
  question: {
@@ -57,6 +67,29 @@ const emit = defineEmits([
57
67
  'change:result',
58
68
  ]);
59
69
 
70
+ // is needed for useVuelidate, because props.question/props.result isn't reactive
71
+ const { question } = toRefs(props);
72
+
73
+ const v$ = useVuelidate(
74
+ computed(() => (
75
+ {
76
+ question: {
77
+ min: {
78
+ minValue: minValue(0),
79
+ maxValue: maxValue(9),
80
+ required,
81
+ },
82
+ max: {
83
+ minValue: minValue(1),
84
+ maxValue: maxValue(10),
85
+ required,
86
+ },
87
+ },
88
+ })),
89
+ { question },
90
+ { $autoDirty: true },
91
+ );
92
+
60
93
  const scoreRange = computed(() => {
61
94
  if (props.question.min > props.question.max) return [];
62
95
  const result = [];
@@ -71,6 +104,9 @@ const scoreRange = computed(() => {
71
104
  function updateQuestion({ path, value }) {
72
105
  emit('change:question', updateObject({ obj: props.question, path, value }));
73
106
  }
107
+
108
+ // init validation
109
+ onMounted(() => v$.value.$touch());
74
110
  </script>
75
111
 
76
112
  <style lang="scss" scoped>
@@ -1,8 +1,8 @@
1
1
  import { generateQuestionScoreSchema } from './AuditFormQuestionScoreSchema';
2
2
 
3
3
  // eslint-disable-next-line import/prefer-default-export
4
- export const generateQuestionSchema = () => ({
5
- required: true,
6
- question: 'Title',
4
+ export const generateQuestionSchema = ({ required = false } = {}) => ({
5
+ required,
6
+ question: '',
7
7
  ...generateQuestionScoreSchema(),
8
8
  });