@webitel/ui-sdk 3.1.79 → 3.1.81
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/package.json +1 -1
- package/src/modules/AuditForm/components/__tests__/audit-form.spec.js +21 -16
- package/src/modules/AuditForm/components/audit-form-question-read-wrapper.vue +9 -2
- package/src/modules/AuditForm/components/audit-form-question.vue +6 -2
- package/src/modules/AuditForm/components/audit-form.vue +31 -4
- package/src/modules/AuditForm/components/questions/score/audit-form-question-score.vue +4 -1
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { ref } from 'vue';
|
|
2
3
|
import AuditForm from '../audit-form.vue';
|
|
3
4
|
import { generateQuestionSchema } from '../../schemas/AuditFormQuestionSchema';
|
|
4
5
|
|
|
@@ -8,11 +9,11 @@ import {
|
|
|
8
9
|
|
|
9
10
|
jest.mock('../../composables/useDestroyableSortable');
|
|
10
11
|
|
|
11
|
-
useDestroyableSortable.mockImplementation(() => ({}));
|
|
12
|
+
useDestroyableSortable.mockImplementation(() => ({ reloadSortable: ref(false) }));
|
|
12
13
|
|
|
13
14
|
describe('AuditForm', () => {
|
|
14
|
-
it('renders a component', () => {
|
|
15
|
-
const wrapper =
|
|
15
|
+
it('renders a component', async () => {
|
|
16
|
+
const wrapper = mount(AuditForm, {
|
|
16
17
|
props: {
|
|
17
18
|
mode: '',
|
|
18
19
|
questions: [],
|
|
@@ -20,47 +21,51 @@ describe('AuditForm', () => {
|
|
|
20
21
|
});
|
|
21
22
|
expect(wrapper.isVisible()).toBe(true);
|
|
22
23
|
});
|
|
23
|
-
it('add question button triggers update question event', () => {
|
|
24
|
-
const wrapper =
|
|
24
|
+
it('add question button triggers update question event', async () => {
|
|
25
|
+
const wrapper = mount(AuditForm, {
|
|
25
26
|
props: {
|
|
26
27
|
mode: 'create',
|
|
27
28
|
questions: [],
|
|
28
29
|
},
|
|
29
30
|
});
|
|
30
|
-
wrapper.findComponent('.audit-form__add-button').vm.$emit('click');
|
|
31
|
+
await wrapper.findComponent('.audit-form__add-button').vm.$emit('click');
|
|
32
|
+
await wrapper.vm.$nextTick();
|
|
33
|
+
await wrapper.vm.$nextTick();
|
|
34
|
+
await wrapper.vm.$nextTick();
|
|
35
|
+
console.info(wrapper.html());
|
|
31
36
|
expect(wrapper.emitted()['update:questions'][0][0])
|
|
32
37
|
.toEqual([generateQuestionSchema({ required: true })]);
|
|
33
38
|
});
|
|
34
|
-
it('delete event from child question emits update without passed question', () => {
|
|
35
|
-
const wrapper =
|
|
39
|
+
it('delete event from child question emits update without passed question', async () => {
|
|
40
|
+
const wrapper = mount(AuditForm, {
|
|
36
41
|
props: {
|
|
37
42
|
mode: 'create',
|
|
38
43
|
questions: [generateQuestionSchema()],
|
|
39
44
|
},
|
|
40
45
|
});
|
|
41
|
-
wrapper.findComponent({ name: 'audit-form-question' }).vm.$emit('delete', { key: 0 });
|
|
46
|
+
await wrapper.findComponent({ name: 'audit-form-question' }).vm.$emit('delete', { key: 0 });
|
|
42
47
|
expect(wrapper.emitted()['update:questions'][0][0])
|
|
43
48
|
.toEqual([]);
|
|
44
49
|
});
|
|
45
|
-
it('copy event from child question emits update with duplicated questions', () => {
|
|
50
|
+
it('copy event from child question emits update with duplicated questions', async () => {
|
|
46
51
|
const question = generateQuestionSchema();
|
|
47
|
-
const wrapper =
|
|
52
|
+
const wrapper = mount(AuditForm, {
|
|
48
53
|
props: {
|
|
49
54
|
mode: 'create',
|
|
50
55
|
questions: [question],
|
|
51
56
|
},
|
|
52
57
|
});
|
|
53
|
-
wrapper.findComponent({ name: 'audit-form-question' }).vm.$emit('copy', { question, key: 0 });
|
|
58
|
+
await wrapper.findComponent({ name: 'audit-form-question' }).vm.$emit('copy', { question, key: 0 });
|
|
54
59
|
expect(wrapper.emitted()['update:questions'][0][0])
|
|
55
60
|
.toEqual([question, question]);
|
|
56
61
|
});
|
|
57
|
-
it('initializes result depending on passed questions',
|
|
58
|
-
const wrapper =
|
|
62
|
+
it('initializes result depending on passed questions',() => {
|
|
63
|
+
const wrapper = mount(AuditForm, {
|
|
59
64
|
props: {
|
|
60
65
|
mode: 'fill',
|
|
61
66
|
questions: [generateQuestionSchema(), generateQuestionSchema(), generateQuestionSchema()],
|
|
62
67
|
},
|
|
63
68
|
});
|
|
64
|
-
expect(wrapper.emitted()['update:result'][0][0]).toEqual([
|
|
69
|
+
expect(wrapper.emitted()['update:result'][0][0]).toEqual([{}, {}, {}]);
|
|
65
70
|
});
|
|
66
71
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<article
|
|
3
3
|
class="audit-form-question-read"
|
|
4
4
|
:class="{
|
|
5
|
-
'audit-form-question-read--filled':
|
|
5
|
+
'audit-form-question-read--filled': isResult && result.score != null,
|
|
6
6
|
'audit-form-question-read--readonly': readonly,
|
|
7
7
|
}"
|
|
8
8
|
@keyup.enter="emit('activate')"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
>{{ question.question }}</p>
|
|
18
18
|
<wt-icon
|
|
19
19
|
class="audit-form-question-read__drag-icon"
|
|
20
|
-
v-if="!disableDragging"
|
|
20
|
+
v-if="!disableDragging && !first"
|
|
21
21
|
icon="move"
|
|
22
22
|
color="secondary"
|
|
23
23
|
></wt-icon>
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
<script setup>
|
|
38
38
|
import { computed } from 'vue';
|
|
39
39
|
import { EngineAuditQuestionType } from 'webitel-sdk';
|
|
40
|
+
import isEmpty from '../../../scripts/isEmpty';
|
|
40
41
|
import AuditFormQuestionOptions from './questions/options/audit-form-question-options.vue';
|
|
41
42
|
import AuditFormQuestionScore from './questions/score/audit-form-question-score.vue';
|
|
42
43
|
import WtIcon from '../../../components/atoms/wt-icon/wt-icon.vue';
|
|
@@ -58,6 +59,10 @@ const props = defineProps({
|
|
|
58
59
|
type: Boolean,
|
|
59
60
|
default: false,
|
|
60
61
|
},
|
|
62
|
+
first: {
|
|
63
|
+
type: Boolean,
|
|
64
|
+
default: false,
|
|
65
|
+
},
|
|
61
66
|
});
|
|
62
67
|
|
|
63
68
|
const emit = defineEmits([
|
|
@@ -70,6 +75,8 @@ const QuestionTypeComponent = computed(() => {
|
|
|
70
75
|
if (props.question.type === EngineAuditQuestionType.Score) return AuditFormQuestionScore;
|
|
71
76
|
return null;
|
|
72
77
|
});
|
|
78
|
+
|
|
79
|
+
const isResult = computed(() => !isEmpty(props.result));
|
|
73
80
|
</script>
|
|
74
81
|
|
|
75
82
|
<style lang="scss" scoped>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
:class="[
|
|
6
6
|
`audit-form-question--mode-${mode}`,
|
|
7
7
|
{
|
|
8
|
-
'audit-form-question--answered':
|
|
8
|
+
'audit-form-question--answered': isResult,
|
|
9
9
|
},
|
|
10
10
|
]"
|
|
11
11
|
:disable-dragging="mode === 'fill'"
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
import { useVuelidate } from '@vuelidate/core';
|
|
28
28
|
import { required } from '@vuelidate/validators';
|
|
29
29
|
import { computed, onMounted, ref, toRefs } from 'vue';
|
|
30
|
+
import isEmpty from '../../../scripts/isEmpty';
|
|
30
31
|
import vClickaway from '../../../directives/clickaway/clickaway';
|
|
31
32
|
import QuestionRead from './audit-form-question-read-wrapper.vue';
|
|
32
33
|
import QuestionWrite from './audit-form-question-write-wrapper.vue';
|
|
@@ -90,6 +91,8 @@ const component = computed(() => {
|
|
|
90
91
|
return QuestionRead;
|
|
91
92
|
});
|
|
92
93
|
|
|
94
|
+
const isResult = computed(() => !isEmpty(props.result));
|
|
95
|
+
|
|
93
96
|
function saveQuestion() {
|
|
94
97
|
state.value = QuestionState.SAVED;
|
|
95
98
|
}
|
|
@@ -99,10 +102,11 @@ function activateQuestion() {
|
|
|
99
102
|
state.value = QuestionState.EDIT;
|
|
100
103
|
}
|
|
101
104
|
|
|
105
|
+
defineExpose({ activateQuestion });
|
|
106
|
+
|
|
102
107
|
// initialize validations
|
|
103
108
|
onMounted(() => {
|
|
104
109
|
v$.value.$touch();
|
|
105
|
-
if (props.first) activateQuestion();
|
|
106
110
|
});
|
|
107
111
|
</script>
|
|
108
112
|
|
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
>
|
|
8
8
|
<audit-form-question
|
|
9
9
|
v-for="(question, key) of questions"
|
|
10
|
+
ref="auditQuestions"
|
|
10
11
|
:key="key"
|
|
11
12
|
:question="question"
|
|
12
13
|
:result="(result && result[key]) ? result[key] : null"
|
|
13
14
|
:mode="mode"
|
|
14
15
|
:first="key === 0"
|
|
15
16
|
:readonly="readonly"
|
|
17
|
+
:class="{'audit-form-question--sort-ignore': key === 0}"
|
|
16
18
|
@copy="copyQuestion({ question, key })"
|
|
17
19
|
@delete="deleteQuestion({ question, key})"
|
|
18
20
|
@update:question="handleQuestionUpdate({ key, value: $event })"
|
|
@@ -33,6 +35,7 @@
|
|
|
33
35
|
import cloneDeep from 'lodash/cloneDeep';
|
|
34
36
|
import {
|
|
35
37
|
watch, watchEffect, ref, computed, onMounted,
|
|
38
|
+
reactive, nextTick,
|
|
36
39
|
} from 'vue';
|
|
37
40
|
import { useVuelidate } from '@vuelidate/core';
|
|
38
41
|
import { useDestroyableSortable } from '../composables/useDestroyableSortable';
|
|
@@ -70,13 +73,17 @@ const emit = defineEmits([
|
|
|
70
73
|
const v$ = useVuelidate();
|
|
71
74
|
|
|
72
75
|
const isInvalidForm = computed(() => !!v$.value.$errors.length);
|
|
76
|
+
const auditQuestions = ref(null);
|
|
77
|
+
const isQuestionAdded = reactive({ value: false, index: null });
|
|
73
78
|
|
|
74
|
-
function addQuestion({ index, question } = {}) {
|
|
79
|
+
async function addQuestion({ index, question } = {}) {
|
|
75
80
|
const questions = [...props.questions];
|
|
76
81
|
const newQuestion = question || generateQuestionSchema();
|
|
77
82
|
if (index != null) questions.splice(index, 0, newQuestion);
|
|
78
83
|
else questions.push(newQuestion);
|
|
79
|
-
emit('update:questions', questions);
|
|
84
|
+
await emit('update:questions', questions);
|
|
85
|
+
isQuestionAdded.value = true;
|
|
86
|
+
isQuestionAdded.index = index || 'last';
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
function handleQuestionUpdate({ key, value }) {
|
|
@@ -98,6 +105,7 @@ function deleteQuestion({ key }) {
|
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
function changeQuestionsOrder({ oldIndex, newIndex }) {
|
|
108
|
+
if (newIndex === 0) return;
|
|
101
109
|
const questions = [...props.questions];
|
|
102
110
|
const [el] = questions.splice(oldIndex, 1);
|
|
103
111
|
questions.splice(newIndex, 0, el);
|
|
@@ -111,14 +119,28 @@ function handleResultUpdate({ key, value }) {
|
|
|
111
119
|
}
|
|
112
120
|
|
|
113
121
|
function initResult() {
|
|
114
|
-
const result = props.questions.map(() =>
|
|
122
|
+
const result = props.questions.map(() => ({}));
|
|
115
123
|
emit('update:result', result);
|
|
116
124
|
}
|
|
117
125
|
|
|
118
126
|
function initQuestions() {
|
|
119
127
|
if (props.mode === 'create' && !props.questions.length) {
|
|
120
128
|
addQuestion({ question: generateQuestionSchema({ required: true }) });
|
|
121
|
-
}
|
|
129
|
+
} else if (props.questions.length) auditQuestions.value.at(0).activateQuestion();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// https://my.webitel.com/browse/WTEL-3451, https://my.webitel.com/browse/WTEL-3436
|
|
133
|
+
// at new question added, activate this question
|
|
134
|
+
async function atQuestionAdded() {
|
|
135
|
+
// wait for new question to render
|
|
136
|
+
await nextTick();
|
|
137
|
+
const index = isQuestionAdded.index && isQuestionAdded.index === 'last'
|
|
138
|
+
? -1
|
|
139
|
+
: isQuestionAdded.index;
|
|
140
|
+
auditQuestions.value.at(index).activateQuestion();
|
|
141
|
+
|
|
142
|
+
isQuestionAdded.value = false;
|
|
143
|
+
isQuestionAdded.index = null;
|
|
122
144
|
}
|
|
123
145
|
|
|
124
146
|
const sortableWrapper = ref(null);
|
|
@@ -126,6 +148,7 @@ const sortableWrapper = ref(null);
|
|
|
126
148
|
const { reloadSortable } = useDestroyableSortable(sortableWrapper, {
|
|
127
149
|
handle: '.audit-form-question-read__drag-icon',
|
|
128
150
|
disabled: props.mode !== 'create',
|
|
151
|
+
filter: '.audit-form-question--sort-ignore',
|
|
129
152
|
onEnd: ({ newIndex, oldIndex }) => {
|
|
130
153
|
if (newIndex === oldIndex) return;
|
|
131
154
|
changeQuestionsOrder({ oldIndex, newIndex });
|
|
@@ -134,6 +157,10 @@ const { reloadSortable } = useDestroyableSortable(sortableWrapper, {
|
|
|
134
157
|
|
|
135
158
|
watch(v$, () => emit('update:validation', { invalid: isInvalidForm.value, v$: v$.value }));
|
|
136
159
|
watchEffect(initResult);
|
|
160
|
+
watch(() => props.questions, () => {
|
|
161
|
+
if (!isQuestionAdded.value) return;
|
|
162
|
+
atQuestionAdded();
|
|
163
|
+
});
|
|
137
164
|
|
|
138
165
|
onMounted(() => {
|
|
139
166
|
initQuestions();
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
:key="value"
|
|
35
35
|
:label="`${value}`"
|
|
36
36
|
:value="value"
|
|
37
|
-
:selected="
|
|
37
|
+
:selected="isResult ? result.score : null"
|
|
38
38
|
@input="emit('change:result', { score: value })"
|
|
39
39
|
></wt-radio>
|
|
40
40
|
</div>
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
import { useVuelidate } from '@vuelidate/core';
|
|
46
46
|
import { maxValue, minValue, required } from '@vuelidate/validators';
|
|
47
47
|
import { computed, onMounted, toRefs } from 'vue';
|
|
48
|
+
import isEmpty from '../../../../../scripts/isEmpty';
|
|
48
49
|
import updateObject from '../../../../../scripts/updateObject';
|
|
49
50
|
import WtRadio from '../../../../../components/molecules/wt-radio/wt-radio.vue';
|
|
50
51
|
import WtInput from '../../../../../components/molecules/wt-input/wt-input.vue';
|
|
@@ -103,6 +104,8 @@ const scoreRange = computed(() => {
|
|
|
103
104
|
return result;
|
|
104
105
|
});
|
|
105
106
|
|
|
107
|
+
const isResult = computed(() => !isEmpty(props.result));
|
|
108
|
+
|
|
106
109
|
function updateQuestion({ path, value }) {
|
|
107
110
|
emit('change:question', updateObject({ obj: props.question, path, value }));
|
|
108
111
|
}
|