@zap-wunschlachen/wl-shared-components 1.0.76 → 1.0.78
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/.github/workflows/playwright.yml +229 -229
- package/.github/workflows/static.yml +61 -61
- package/.github/workflows/update-snapshots.yml +37 -37
- package/.prettierrc.json +8 -8
- package/.storybook/main.ts +18 -18
- package/.storybook/preview.ts +37 -37
- package/.storybook/storyWrapper.vue +18 -18
- package/.storybook/withVuetifyTheme.decorator.ts +21 -21
- package/App.vue +139 -139
- package/README.md +56 -56
- package/docs/assets.md +62 -62
- package/heroicons.ts +75 -75
- package/index.html +19 -19
- package/package.json +71 -71
- package/playwright.config.ts +48 -48
- package/public/background.svg +60 -60
- package/public/style.css +187 -187
- package/public/technologies.svg +22 -22
- package/scripts/check-translations.ts +352 -352
- package/src/assets/css/base.css +242 -242
- package/src/assets/css/variables.css +176 -176
- package/src/components/Accordion/Accordion.css +65 -65
- package/src/components/Accordion/AccordionGroup.vue +88 -88
- package/src/components/Accordion/AccordionItem.vue +272 -272
- package/src/components/Accordion/presets/default.css +4 -4
- package/src/components/Accordion/presets/elevated.css +25 -25
- package/src/components/Accordion/presets/filled.css +26 -26
- package/src/components/Accordion/presets/index.css +5 -5
- package/src/components/Accordion/presets/plain.css +34 -34
- package/src/components/Appointment/Card/Actions.css +54 -54
- package/src/components/Appointment/Card/Actions.vue +99 -99
- package/src/components/Appointment/Card/AnamneseNotification.css +20 -20
- package/src/components/Appointment/Card/AnamneseNotification.vue +23 -23
- package/src/components/Appointment/Card/Card.css +99 -99
- package/src/components/Appointment/Card/Card.vue +97 -97
- package/src/components/Appointment/Card/Details.css +62 -62
- package/src/components/Appointment/Card/Details.vue +44 -44
- package/src/components/Audio/Audio.vue +187 -187
- package/src/components/Audio/Waveform.vue +118 -118
- package/src/components/Banner/Banner.css +29 -29
- package/src/components/Banner/Banner.vue +89 -89
- package/src/components/Button/Button.vue +257 -257
- package/src/components/CheckBox/CheckBox.css +234 -234
- package/src/components/CheckBox/Checkbox.vue +184 -184
- package/src/components/DateInput/DateInput.css +2 -2
- package/src/components/DateInput/DateInput.vue +376 -370
- package/src/components/Dialog/Dialog.css +6 -6
- package/src/components/Dialog/Dialog.vue +46 -46
- package/src/components/EditField/EditField.css +19 -19
- package/src/components/EditField/EditField.vue +211 -211
- package/src/components/ErrorPage/ErrorPage.css +172 -172
- package/src/components/IconBullet/IconBullet.vue +104 -104
- package/src/components/IconBullet/IconBulletList.vue +55 -55
- package/src/components/Icons/AdvanceAppointments.vue +161 -161
- package/src/components/Icons/Audio/CloudFailed.vue +27 -27
- package/src/components/Icons/Audio/CloudSaved.vue +28 -28
- package/src/components/Icons/Audio/Delete.vue +22 -22
- package/src/components/Icons/Audio/Pause.vue +25 -25
- package/src/components/Icons/Audio/Play.vue +22 -22
- package/src/components/Icons/Calendar.vue +28 -28
- package/src/components/Icons/CalendarNotification.vue +137 -137
- package/src/components/Icons/Chair.vue +43 -43
- package/src/components/Icons/ChairNotification.vue +46 -46
- package/src/components/Icons/Circle.vue +66 -66
- package/src/components/Icons/FavIcon.vue +69 -69
- package/src/components/Icons/FilledCircle.vue +11 -11
- package/src/components/Icons/Group3.vue +57 -57
- package/src/components/Icons/Play.vue +16 -16
- package/src/components/Icons/RingNotification.vue +65 -65
- package/src/components/Icons/SolidArrowRight.vue +14 -14
- package/src/components/Icons/checkbox.vue +19 -19
- package/src/components/Icons/outlineChecked.vue +38 -38
- package/src/components/Input/Input.css +234 -234
- package/src/components/Input/Input.vue +281 -281
- package/src/components/Laboratory/AppointmentCard/AppointmentCard.css +7 -7
- package/src/components/Laboratory/AppointmentCard/AppointmentCard.vue +116 -116
- package/src/components/Laboratory/ChatBoxImage/ChatBoxImage.vue +81 -81
- package/src/components/Laboratory/ChatMessage/ChatMessage.vue +113 -113
- package/src/components/Laboratory/ChatMessage/ChatMessageBadge.css +4 -4
- package/src/components/Laboratory/ChatMessage/ChatMessageBadge.vue +99 -99
- package/src/components/Laboratory/ChatNotification/ChatNotification.vue +130 -130
- package/src/components/Laboratory/DocumentCard/DocumentCard.css +3 -3
- package/src/components/Laboratory/DocumentCard/DocumentCard.vue +50 -50
- package/src/components/Laboratory/DocumentCard/DocumentCardItem.vue +53 -53
- package/src/components/Laboratory/InfoCard/InfoCard.vue +162 -162
- package/src/components/Laboratory/MainColumnsBar/MainColumnsBar.vue +102 -102
- package/src/components/Laboratory/ProgressCircle/ProgressCircle.vue +152 -152
- package/src/components/Laboratory/ProgressLinear/ProgressLinear.css +33 -33
- package/src/components/Laboratory/ProgressLinear/ProgressLinear.vue +75 -75
- package/src/components/Laboratory/SelectionColumnBar/SelectionColumnBar.vue +92 -92
- package/src/components/Laboratory/StatusNotification/StatusNotification.vue +49 -49
- package/src/components/Laboratory/TagLabel/TagLabel.vue +126 -126
- package/src/components/Laboratory/TagLabelGroup/TagLabelGroup.vue +97 -97
- package/src/components/Laboratory/TicketCard/TicketCard.css +3 -3
- package/src/components/Laboratory/TicketCard/TicketCard.vue +143 -143
- package/src/components/Laboratory/TimeLine/TimeLineEvent.css +18 -18
- package/src/components/Laboratory/TimeLine/TimeLineEvent.vue +119 -119
- package/src/components/Laboratory/TimeLine/Timeline.css +4 -4
- package/src/components/Laboratory/TimeLine/Timeline.vue +30 -30
- package/src/components/Loader/Loader.css +78 -78
- package/src/components/MaintenanceBanner/MaintenanceBanner.css +353 -353
- package/src/components/MaintenanceBanner/MaintenanceBanner.vue +140 -140
- package/src/components/MaintenanceBanner/MaintenanceIllustration.vue +54 -54
- package/src/components/Modal/Modal.css +5 -5
- package/src/components/Modal/Modal.vue +29 -29
- package/src/components/NotificationBubble/NotificationBubble.css +4 -4
- package/src/components/NotificationBubble/NotificationBubble.vue +90 -90
- package/src/components/OtpInput/OtpInput.css +43 -43
- package/src/components/OtpInput/OtpInput.vue +181 -181
- package/src/components/PhoneInput/PhoneInput.css +151 -126
- package/src/components/PhoneInput/PhoneInput.vue +230 -139
- package/src/components/RadioGroup/RadioGroup.css +65 -0
- package/src/components/RadioGroup/RadioGroup.vue +134 -0
- package/src/components/Select/Select.css +172 -172
- package/src/components/Select/Select.vue +377 -377
- package/src/components/SelectAutocomplete/SelectAutocomplete.css +172 -172
- package/src/components/SelectAutocomplete/SelectAutocomplete.vue +414 -414
- package/src/components/TextArea/TextArea.css +269 -269
- package/src/components/TextArea/TextArea.vue +207 -207
- package/src/components/TickBox/TickBox.css +116 -116
- package/src/components/TickBox/TickBox.vue +172 -172
- package/src/components/Tile/Tile.css +106 -106
- package/src/components/Tile/Tile.vue +173 -173
- package/src/components/accessibility.css +218 -218
- package/src/components/index.ts +110 -109
- package/src/constants/iconEnums.ts +3 -3
- package/src/i18n/i18n.ts +15 -15
- package/src/i18n/locales/de.json +30 -30
- package/src/i18n/locales/en.json +30 -30
- package/src/index.ts +43 -43
- package/src/main.ts +11 -11
- package/src/pages/AccordionGroupPage.vue +873 -873
- package/src/pages/AllPage.vue +2483 -2365
- package/src/pages/SelectPage.vue +1302 -1302
- package/src/pages/TilePage.vue +902 -902
- package/src/plugins/vuetify.ts +54 -54
- package/src/shims-vue.d.ts +30 -30
- package/src/utils/index.ts +733 -733
- package/src/vite-env.d.ts +1 -1
- package/tests/unit/accessibility/component-a11y.spec.ts +657 -657
- package/tests/unit/components/Accordion/AccordionGroup.spec.ts +228 -228
- package/tests/unit/components/Accordion/AccordionItem.spec.ts +257 -257
- package/tests/unit/components/Appointment/AnamneseNotification.spec.ts +176 -176
- package/tests/unit/components/Appointment/Card/Actions.spec.ts +436 -436
- package/tests/unit/components/Appointment/Card/Card.spec.ts +531 -531
- package/tests/unit/components/Appointment/Card/Details.spec.ts +395 -395
- package/tests/unit/components/Audio/Audio.spec.ts +403 -403
- package/tests/unit/components/Audio/Waveform.spec.ts +483 -483
- package/tests/unit/components/Background/Background.spec.ts +177 -177
- package/tests/unit/components/Core/AnamneseAnswerDialog.spec.ts +344 -0
- package/tests/unit/components/Core/Banner.spec.ts +187 -0
- package/tests/unit/components/Core/Button.spec.ts +346 -346
- package/tests/unit/components/Core/Checkbox.spec.ts +544 -544
- package/tests/unit/components/Core/DateInput.spec.ts +702 -702
- package/tests/unit/components/Core/Dialog.spec.ts +448 -448
- package/tests/unit/components/Core/EditField.spec.ts +541 -541
- package/tests/unit/components/Core/Input.spec.ts +512 -512
- package/tests/unit/components/Core/List.spec.ts +163 -0
- package/tests/unit/components/Core/ListItem.spec.ts +205 -0
- package/tests/unit/components/Core/Modal.spec.ts +518 -518
- package/tests/unit/components/Core/NotificationBubble.spec.ts +606 -606
- package/tests/unit/components/Core/OtpInput.spec.ts +708 -708
- package/tests/unit/components/Core/PhoneInput.spec.ts +757 -619
- package/tests/unit/components/Core/RadioGroup.spec.ts +318 -0
- package/tests/unit/components/Core/Select.spec.ts +712 -712
- package/tests/unit/components/Core/SelectAutocomplete.spec.ts +361 -0
- package/tests/unit/components/Core/TextArea.spec.ts +565 -565
- package/tests/unit/components/Core/TickBox.spec.ts +836 -836
- package/tests/unit/components/Core/Tile.spec.ts +286 -0
- package/tests/unit/components/DateInput/DateInput.spec.ts +128 -0
- package/tests/unit/components/ErrorPage/ErrorPage.spec.ts +313 -313
- package/tests/unit/components/ErrorPage/ErrorPageLogo.spec.ts +153 -153
- package/tests/unit/components/IconBullet/IconBullet.spec.ts +356 -356
- package/tests/unit/components/IconBullet/IconBulletList.spec.ts +371 -371
- package/tests/unit/components/Icons/AdvanceAppointments.spec.ts +186 -186
- package/tests/unit/components/Icons/Audio/CloudFailed.spec.ts +108 -108
- package/tests/unit/components/Icons/Audio/CloudSaved.spec.ts +149 -149
- package/tests/unit/components/Icons/Audio/Delete.spec.ts +158 -158
- package/tests/unit/components/Icons/Audio/Pause.spec.ts +208 -208
- package/tests/unit/components/Icons/Audio/Play.spec.ts +217 -217
- package/tests/unit/components/Icons/CalendarNotification.spec.ts +193 -193
- package/tests/unit/components/Icons/Chair.spec.ts +241 -241
- package/tests/unit/components/Icons/ChairNotification.spec.ts +318 -318
- package/tests/unit/components/Icons/Circle.spec.ts +255 -255
- package/tests/unit/components/Icons/FavIcon.spec.ts +259 -259
- package/tests/unit/components/Icons/FilledCircle.spec.ts +274 -274
- package/tests/unit/components/Icons/Group3.spec.ts +362 -362
- package/tests/unit/components/Icons/Logo.spec.ts +229 -229
- package/tests/unit/components/Icons/MiniLogo.spec.ts +38 -38
- package/tests/unit/components/Icons/RingNotification.spec.ts +400 -400
- package/tests/unit/components/Icons/SolidArrowRight.spec.ts +49 -49
- package/tests/unit/components/Icons/calendar.spec.ts +293 -293
- package/tests/unit/components/Icons/checkbox.spec.ts +315 -315
- package/tests/unit/components/Icons/outlineChecked.spec.ts +441 -441
- package/tests/unit/components/Icons/play.spec.ts +315 -315
- package/tests/unit/components/Laboratory/AppointmentCard.spec.ts +167 -167
- package/tests/unit/components/Laboratory/ChatBoxImage.spec.ts +179 -179
- package/tests/unit/components/Laboratory/ChatMessage.spec.ts +263 -263
- package/tests/unit/components/Laboratory/ChatMessageBadge.spec.ts +282 -282
- package/tests/unit/components/Laboratory/ChatNotification.spec.ts +256 -256
- package/tests/unit/components/Laboratory/DocumentCard.spec.ts +228 -228
- package/tests/unit/components/Laboratory/DocumentCardItem.spec.ts +236 -236
- package/tests/unit/components/Laboratory/InfoCard.spec.ts +308 -308
- package/tests/unit/components/Laboratory/MainColumnsBar.spec.ts +251 -251
- package/tests/unit/components/Laboratory/ProgressCircle.spec.ts +290 -290
- package/tests/unit/components/Laboratory/ProgressLinear.spec.ts +275 -275
- package/tests/unit/components/Laboratory/SelectionColumnBar.spec.ts +288 -288
- package/tests/unit/components/Laboratory/StatusNotification.spec.ts +296 -296
- package/tests/unit/components/Laboratory/TagLabel.spec.ts +353 -353
- package/tests/unit/components/Laboratory/TagLabelGroup.spec.ts +377 -377
- package/tests/unit/components/Laboratory/TicketCard.spec.ts +351 -351
- package/tests/unit/components/Laboratory/TimeLineEvent.spec.ts +381 -381
- package/tests/unit/components/Laboratory/Timeline.spec.ts +419 -419
- package/tests/unit/components/Loader/Loader.spec.ts +197 -197
- package/tests/unit/components/MaintenanceBanner/MaintenanceBanner.spec.ts +302 -302
- package/tests/unit/constants/iconEnums.spec.ts +39 -39
- package/tests/unit/i18n/i18n.spec.ts +88 -88
- package/tests/unit/plugins/vuetify.spec.ts +182 -182
- package/tests/unit/setup.ts +237 -237
- package/tests/unit/src/components/index.spec.ts.skip +192 -192
- package/tests/unit/src/index.spec.ts.skip +182 -182
- package/tests/unit/src/main.spec.ts +111 -111
- package/tests/unit/utils/accessibility.spec.ts +318 -318
- package/tests/unit/utils/anamnese.spec.ts +531 -0
- package/tsconfig.json +26 -26
- package/vite.config.ts +29 -29
- package/vitest.config.ts +91 -91
|
@@ -1,541 +1,541 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { mount, flushPromises } from '@vue/test-utils';
|
|
3
|
-
import { nextTick } from 'vue';
|
|
4
|
-
import EditField from '@components/EditField/EditField.vue';
|
|
5
|
-
|
|
6
|
-
// Mock siteColors
|
|
7
|
-
vi.mock('@/utils/index', () => ({
|
|
8
|
-
siteColors: {
|
|
9
|
-
'primary-color': '#172774',
|
|
10
|
-
'font_color_title_code': '#333333'
|
|
11
|
-
}
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
describe('EditField', () => {
|
|
15
|
-
const createGlobalConfig = () => ({
|
|
16
|
-
stubs: {
|
|
17
|
-
Input: {
|
|
18
|
-
name: 'Input',
|
|
19
|
-
template: '<input class="input-stub" data-testid="input-component" @blur="$emit(\'blur\')" @focus="$emit(\'focus\')" @keyup.enter="$emit(\'keyup\', { key: \'Enter\' })" @keyup.esc="$emit(\'keyup\', { key: \'Escape\' })" />',
|
|
20
|
-
props: ['modelValue', 'reverse', 'borderOnHover'],
|
|
21
|
-
emits: ['blur', 'focus', 'keyup', 'update:modelValue'],
|
|
22
|
-
methods: {
|
|
23
|
-
focus() {},
|
|
24
|
-
blur() {},
|
|
25
|
-
select() {}
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
Select: {
|
|
29
|
-
name: 'Select',
|
|
30
|
-
template: '<select class="select-stub" data-testid="select-component"></select>',
|
|
31
|
-
props: ['modelValue', 'items', 'reverse', 'label', 'returnObject', 'borderOnHover'],
|
|
32
|
-
emits: ['blur', 'focus', 'update:model-value'],
|
|
33
|
-
methods: {
|
|
34
|
-
focus() {},
|
|
35
|
-
blur() {}
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
DateInput: {
|
|
39
|
-
name: 'DateInput',
|
|
40
|
-
template: '<input class="date-input-stub" data-testid="date-input-component" />',
|
|
41
|
-
props: ['modelValue', 'reverse', 'layout', 'borderOnHover'],
|
|
42
|
-
emits: ['blur', 'focus', 'update:modelValue'],
|
|
43
|
-
methods: {
|
|
44
|
-
focus() {},
|
|
45
|
-
blur() {}
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
PhoneInput: {
|
|
49
|
-
name: 'PhoneInput',
|
|
50
|
-
template: '<input class="phone-input-stub" data-testid="phone-input-component" />',
|
|
51
|
-
props: ['modelValue', 'density', 'hideDetails', 'reverse', 'borderOnHover', 'appendInnerIcon'],
|
|
52
|
-
emits: ['blur', 'focus', 'update:phone'],
|
|
53
|
-
methods: {
|
|
54
|
-
focus() {},
|
|
55
|
-
blur() {}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
provide: {
|
|
60
|
-
themeColors: {
|
|
61
|
-
value: {
|
|
62
|
-
'primary-color': '#172774',
|
|
63
|
-
'font_color_title_code': '#333333'
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
beforeEach(() => {
|
|
70
|
-
vi.clearAllMocks();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe('Default Behavior', () => {
|
|
74
|
-
it('renders with required props', () => {
|
|
75
|
-
const wrapper = mount(EditField, {
|
|
76
|
-
props: {
|
|
77
|
-
title: 'Test Field',
|
|
78
|
-
value: 'test value'
|
|
79
|
-
},
|
|
80
|
-
global: createGlobalConfig()
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
84
|
-
expect(wrapper.find('.wl-edit-field').exists()).toBe(true);
|
|
85
|
-
expect(wrapper.find('.title-container').exists()).toBe(true);
|
|
86
|
-
expect(wrapper.find('.input-wrapper').exists()).toBe(true);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('displays title correctly', () => {
|
|
90
|
-
const wrapper = mount(EditField, {
|
|
91
|
-
props: {
|
|
92
|
-
title: 'Field Title',
|
|
93
|
-
value: 'test'
|
|
94
|
-
},
|
|
95
|
-
global: createGlobalConfig()
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
expect(wrapper.find('.title-container p').text()).toBe('Field Title');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('has data-testid for testing', () => {
|
|
102
|
-
const wrapper = mount(EditField, {
|
|
103
|
-
props: {
|
|
104
|
-
title: 'Test',
|
|
105
|
-
value: 'test'
|
|
106
|
-
},
|
|
107
|
-
global: createGlobalConfig()
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
describe('Icon', () => {
|
|
115
|
-
it('displays icon when provided', () => {
|
|
116
|
-
const wrapper = mount(EditField, {
|
|
117
|
-
props: {
|
|
118
|
-
title: 'With Icon',
|
|
119
|
-
value: 'test',
|
|
120
|
-
icon: 'mdi-home'
|
|
121
|
-
},
|
|
122
|
-
global: createGlobalConfig()
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
const titleIcon = wrapper.find('.title-container .v-icon');
|
|
126
|
-
expect(titleIcon.exists()).toBe(true);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('hides icon when empty string', () => {
|
|
130
|
-
const wrapper = mount(EditField, {
|
|
131
|
-
props: {
|
|
132
|
-
title: 'No Icon',
|
|
133
|
-
value: 'test',
|
|
134
|
-
icon: ''
|
|
135
|
-
},
|
|
136
|
-
global: createGlobalConfig()
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
expect(wrapper.find('.title-container .v-icon').exists()).toBe(false);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('displays default append icon when not specified', () => {
|
|
143
|
-
const wrapper = mount(EditField, {
|
|
144
|
-
props: {
|
|
145
|
-
title: 'Default Append Icon',
|
|
146
|
-
value: 'test'
|
|
147
|
-
},
|
|
148
|
-
global: createGlobalConfig()
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
const appendIcon = wrapper.find('.input-wrapper .v-icon');
|
|
152
|
-
expect(appendIcon.exists()).toBe(true);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('hides append icon when explicitly set to empty string', () => {
|
|
156
|
-
const wrapper = mount(EditField, {
|
|
157
|
-
props: {
|
|
158
|
-
title: 'No Append Icon',
|
|
159
|
-
value: 'test',
|
|
160
|
-
appendIcon: ''
|
|
161
|
-
},
|
|
162
|
-
global: createGlobalConfig()
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// When appendIcon is empty string, computedAppendIcon returns empty string
|
|
166
|
-
// and v-if="computedAppendIcon" evaluates to false
|
|
167
|
-
const appendIcons = wrapper.findAll('.input-wrapper > .v-icon');
|
|
168
|
-
expect(appendIcons.length).toBe(0);
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
describe('Input Types', () => {
|
|
173
|
-
it('renders text input when type is "text"', () => {
|
|
174
|
-
const wrapper = mount(EditField, {
|
|
175
|
-
props: {
|
|
176
|
-
title: 'Text Field',
|
|
177
|
-
value: 'text value',
|
|
178
|
-
type: 'text'
|
|
179
|
-
},
|
|
180
|
-
global: createGlobalConfig()
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
expect(wrapper.find('[data-testid="input-component"]').exists()).toBe(true);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('renders select input when type is "select"', () => {
|
|
187
|
-
const items = [
|
|
188
|
-
{ value: 'opt1', title: 'Option 1' },
|
|
189
|
-
{ value: 'opt2', title: 'Option 2' }
|
|
190
|
-
];
|
|
191
|
-
|
|
192
|
-
const wrapper = mount(EditField, {
|
|
193
|
-
props: {
|
|
194
|
-
title: 'Select Field',
|
|
195
|
-
value: 'opt1',
|
|
196
|
-
type: 'select',
|
|
197
|
-
items
|
|
198
|
-
},
|
|
199
|
-
global: createGlobalConfig()
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
expect(wrapper.find('[data-testid="select-component"]').exists()).toBe(true);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it('renders date input when type is "date"', () => {
|
|
206
|
-
const wrapper = mount(EditField, {
|
|
207
|
-
props: {
|
|
208
|
-
title: 'Date Field',
|
|
209
|
-
value: '2023-12-25',
|
|
210
|
-
type: 'date'
|
|
211
|
-
},
|
|
212
|
-
global: createGlobalConfig()
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
expect(wrapper.find('[data-testid="date-input-component"]').exists()).toBe(true);
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('renders phone input when type is "phone"', () => {
|
|
219
|
-
const wrapper = mount(EditField, {
|
|
220
|
-
props: {
|
|
221
|
-
title: 'Phone Field',
|
|
222
|
-
value: '+1234567890',
|
|
223
|
-
type: 'phone'
|
|
224
|
-
},
|
|
225
|
-
global: createGlobalConfig()
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
expect(wrapper.find('[data-testid="phone-input-component"]').exists()).toBe(true);
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
describe('v-model', () => {
|
|
233
|
-
it('updates internalValue when value prop changes', async () => {
|
|
234
|
-
const wrapper = mount(EditField, {
|
|
235
|
-
props: {
|
|
236
|
-
title: 'Test',
|
|
237
|
-
value: 'initial'
|
|
238
|
-
},
|
|
239
|
-
global: createGlobalConfig()
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
await wrapper.setProps({ value: 'updated' });
|
|
243
|
-
await nextTick();
|
|
244
|
-
|
|
245
|
-
// The watcher should have updated internalValue
|
|
246
|
-
expect(wrapper.vm.internalValue).toBe('updated');
|
|
247
|
-
});
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
describe('Editing Functionality', () => {
|
|
251
|
-
it('reverts value on Escape key via onEsc method', () => {
|
|
252
|
-
const wrapper = mount(EditField, {
|
|
253
|
-
props: {
|
|
254
|
-
title: 'Test',
|
|
255
|
-
value: 'original'
|
|
256
|
-
},
|
|
257
|
-
global: createGlobalConfig()
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
// Simulate changing value
|
|
261
|
-
wrapper.vm.internalValue = 'modified';
|
|
262
|
-
|
|
263
|
-
// Call onEsc
|
|
264
|
-
wrapper.vm.onEsc();
|
|
265
|
-
|
|
266
|
-
expect(wrapper.vm.internalValue).toBe('original');
|
|
267
|
-
expect(wrapper.vm.skipSave).toBe(true);
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
it('calls save on blur when skipSave is false', () => {
|
|
271
|
-
const wrapper = mount(EditField, {
|
|
272
|
-
props: {
|
|
273
|
-
title: 'Test',
|
|
274
|
-
value: 'original'
|
|
275
|
-
},
|
|
276
|
-
global: createGlobalConfig()
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
wrapper.vm.internalValue = 'modified';
|
|
280
|
-
wrapper.vm.skipSave = false;
|
|
281
|
-
|
|
282
|
-
wrapper.vm.handleBlur();
|
|
283
|
-
|
|
284
|
-
expect(wrapper.emitted('update:value')).toBeTruthy();
|
|
285
|
-
expect(wrapper.emitted('update:value')?.[0]).toEqual(['modified']);
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
it('does not save on blur when skipSave is true', () => {
|
|
289
|
-
const wrapper = mount(EditField, {
|
|
290
|
-
props: {
|
|
291
|
-
title: 'Test',
|
|
292
|
-
value: 'original'
|
|
293
|
-
},
|
|
294
|
-
global: createGlobalConfig()
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
wrapper.vm.internalValue = 'modified';
|
|
298
|
-
wrapper.vm.skipSave = true;
|
|
299
|
-
|
|
300
|
-
wrapper.vm.handleBlur();
|
|
301
|
-
|
|
302
|
-
expect(wrapper.emitted('update:value')).toBeFalsy();
|
|
303
|
-
expect(wrapper.vm.skipSave).toBe(false);
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it('does not emit update:value when internalValue equals prop value', () => {
|
|
307
|
-
const wrapper = mount(EditField, {
|
|
308
|
-
props: {
|
|
309
|
-
title: 'Test',
|
|
310
|
-
value: 'same'
|
|
311
|
-
},
|
|
312
|
-
global: createGlobalConfig()
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
wrapper.vm.internalValue = 'same';
|
|
316
|
-
wrapper.vm.save();
|
|
317
|
-
|
|
318
|
-
expect(wrapper.emitted('update:value')).toBeFalsy();
|
|
319
|
-
});
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
describe('Slot Functionality', () => {
|
|
323
|
-
it('renders input slot when provided', () => {
|
|
324
|
-
const wrapper = mount(EditField, {
|
|
325
|
-
props: {
|
|
326
|
-
title: 'Custom Slot',
|
|
327
|
-
value: 'test'
|
|
328
|
-
},
|
|
329
|
-
global: createGlobalConfig(),
|
|
330
|
-
slots: {
|
|
331
|
-
input: '<div data-testid="custom-input">Custom Input Content</div>'
|
|
332
|
-
}
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
expect(wrapper.find('[data-testid="custom-input"]').exists()).toBe(true);
|
|
336
|
-
expect(wrapper.find('[data-testid="custom-input"]').text()).toBe('Custom Input Content');
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
it('passes isInputFocused to input slot', () => {
|
|
340
|
-
const wrapper = mount(EditField, {
|
|
341
|
-
props: {
|
|
342
|
-
title: 'Slot Props',
|
|
343
|
-
value: 'test'
|
|
344
|
-
},
|
|
345
|
-
global: createGlobalConfig(),
|
|
346
|
-
slots: {
|
|
347
|
-
input: `
|
|
348
|
-
<template #input="{ isInputFocused }">
|
|
349
|
-
<div data-testid="focused-indicator">
|
|
350
|
-
Focused: {{ isInputFocused }}
|
|
351
|
-
</div>
|
|
352
|
-
</template>
|
|
353
|
-
`
|
|
354
|
-
}
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
const indicator = wrapper.find('[data-testid="focused-indicator"]');
|
|
358
|
-
expect(indicator.exists()).toBe(true);
|
|
359
|
-
expect(indicator.text()).toContain('Focused: false');
|
|
360
|
-
});
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
describe('Accessibility', () => {
|
|
364
|
-
it('has proper tabindex on title container', () => {
|
|
365
|
-
const wrapper = mount(EditField, {
|
|
366
|
-
props: {
|
|
367
|
-
title: 'Accessibility Test',
|
|
368
|
-
value: 'test'
|
|
369
|
-
},
|
|
370
|
-
global: createGlobalConfig()
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
const titleContainer = wrapper.find('.title-container');
|
|
374
|
-
expect(titleContainer.attributes('tabindex')).toBe('-1');
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
it('provides meaningful title text', () => {
|
|
378
|
-
const wrapper = mount(EditField, {
|
|
379
|
-
props: {
|
|
380
|
-
title: 'User Email Address',
|
|
381
|
-
value: 'user@example.com'
|
|
382
|
-
},
|
|
383
|
-
global: createGlobalConfig()
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
expect(wrapper.find('.title-container p').text()).toBe('User Email Address');
|
|
387
|
-
});
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
describe('Edge Cases', () => {
|
|
391
|
-
it('handles empty string values', () => {
|
|
392
|
-
const wrapper = mount(EditField, {
|
|
393
|
-
props: {
|
|
394
|
-
title: 'Empty Value',
|
|
395
|
-
value: ''
|
|
396
|
-
},
|
|
397
|
-
global: createGlobalConfig()
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
expect(wrapper.vm.internalValue).toBe('');
|
|
401
|
-
|
|
402
|
-
wrapper.vm.internalValue = 'new value';
|
|
403
|
-
wrapper.vm.save();
|
|
404
|
-
|
|
405
|
-
expect(wrapper.emitted('update:value')?.[0]).toEqual(['new value']);
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
it('handles special characters in values', () => {
|
|
409
|
-
const specialValue = '!@#$%^&*()_+{}|:"<>?`~[]\\;\',./ ñáéíóú';
|
|
410
|
-
const wrapper = mount(EditField, {
|
|
411
|
-
props: {
|
|
412
|
-
title: 'Special Chars',
|
|
413
|
-
value: specialValue
|
|
414
|
-
},
|
|
415
|
-
global: createGlobalConfig()
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
expect(wrapper.vm.internalValue).toBe(specialValue);
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
it('handles number values', () => {
|
|
422
|
-
const wrapper = mount(EditField, {
|
|
423
|
-
props: {
|
|
424
|
-
title: 'Number Value',
|
|
425
|
-
value: 42
|
|
426
|
-
},
|
|
427
|
-
global: createGlobalConfig()
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
expect(wrapper.vm.internalValue).toBe(42);
|
|
431
|
-
|
|
432
|
-
wrapper.vm.internalValue = 100;
|
|
433
|
-
wrapper.vm.save();
|
|
434
|
-
|
|
435
|
-
expect(wrapper.emitted('update:value')?.[0]).toEqual([100]);
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
it('handles boolean values', () => {
|
|
439
|
-
const wrapper = mount(EditField, {
|
|
440
|
-
props: {
|
|
441
|
-
title: 'Boolean Value',
|
|
442
|
-
value: true
|
|
443
|
-
},
|
|
444
|
-
global: createGlobalConfig()
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
expect(wrapper.vm.internalValue).toBe(true);
|
|
448
|
-
|
|
449
|
-
wrapper.vm.internalValue = false;
|
|
450
|
-
wrapper.vm.save();
|
|
451
|
-
|
|
452
|
-
expect(wrapper.emitted('update:value')?.[0]).toEqual([false]);
|
|
453
|
-
});
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
describe('Complex Scenarios', () => {
|
|
457
|
-
it('handles escape during editing', () => {
|
|
458
|
-
const wrapper = mount(EditField, {
|
|
459
|
-
props: {
|
|
460
|
-
title: 'Escape Test',
|
|
461
|
-
value: 'original'
|
|
462
|
-
},
|
|
463
|
-
global: createGlobalConfig()
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
// Start editing and change value
|
|
467
|
-
wrapper.vm.internalValue = 'modified';
|
|
468
|
-
|
|
469
|
-
// Press escape
|
|
470
|
-
wrapper.vm.onEsc();
|
|
471
|
-
|
|
472
|
-
expect(wrapper.vm.internalValue).toBe('original');
|
|
473
|
-
expect(wrapper.vm.skipSave).toBe(true);
|
|
474
|
-
|
|
475
|
-
// Simulate blur after escape
|
|
476
|
-
wrapper.vm.handleBlur();
|
|
477
|
-
|
|
478
|
-
// Should not emit update because skipSave was true
|
|
479
|
-
expect(wrapper.emitted('update:value')).toBeFalsy();
|
|
480
|
-
expect(wrapper.vm.skipSave).toBe(false);
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
it('works with select type and items', () => {
|
|
484
|
-
const items = [
|
|
485
|
-
{ value: 'opt1', title: 'Option 1' },
|
|
486
|
-
{ value: 'opt2', title: 'Option 2' },
|
|
487
|
-
{ value: 'opt3', title: 'Option 3' }
|
|
488
|
-
];
|
|
489
|
-
|
|
490
|
-
const wrapper = mount(EditField, {
|
|
491
|
-
props: {
|
|
492
|
-
title: 'Select Test',
|
|
493
|
-
value: 'opt1',
|
|
494
|
-
type: 'select',
|
|
495
|
-
items
|
|
496
|
-
},
|
|
497
|
-
global: createGlobalConfig()
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
expect(wrapper.find('[data-testid="select-component"]').exists()).toBe(true);
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
it('passes inputProps to child components', () => {
|
|
504
|
-
const inputProps = {
|
|
505
|
-
placeholder: 'Enter text here',
|
|
506
|
-
disabled: false
|
|
507
|
-
};
|
|
508
|
-
|
|
509
|
-
const wrapper = mount(EditField, {
|
|
510
|
-
props: {
|
|
511
|
-
title: 'Props Test',
|
|
512
|
-
value: 'test',
|
|
513
|
-
type: 'text',
|
|
514
|
-
inputProps
|
|
515
|
-
},
|
|
516
|
-
global: createGlobalConfig()
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
expect(wrapper.find('[data-testid="input-component"]').exists()).toBe(true);
|
|
520
|
-
});
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
describe('Append Icon Click', () => {
|
|
524
|
-
it('clicking append icon triggers onStartEditing', async () => {
|
|
525
|
-
const wrapper = mount(EditField, {
|
|
526
|
-
props: {
|
|
527
|
-
title: 'Test',
|
|
528
|
-
value: 'test'
|
|
529
|
-
},
|
|
530
|
-
global: createGlobalConfig()
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
const appendIcon = wrapper.find('.input-wrapper > .v-icon');
|
|
534
|
-
expect(appendIcon.exists()).toBe(true);
|
|
535
|
-
|
|
536
|
-
// The onStartEditing calls inputElementRef?.focus?.()
|
|
537
|
-
// Since we're using stubs, we verify the click is handled
|
|
538
|
-
await appendIcon.trigger('click');
|
|
539
|
-
});
|
|
540
|
-
});
|
|
541
|
-
});
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { mount, flushPromises } from '@vue/test-utils';
|
|
3
|
+
import { nextTick } from 'vue';
|
|
4
|
+
import EditField from '@components/EditField/EditField.vue';
|
|
5
|
+
|
|
6
|
+
// Mock siteColors
|
|
7
|
+
vi.mock('@/utils/index', () => ({
|
|
8
|
+
siteColors: {
|
|
9
|
+
'primary-color': '#172774',
|
|
10
|
+
'font_color_title_code': '#333333'
|
|
11
|
+
}
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
describe('EditField', () => {
|
|
15
|
+
const createGlobalConfig = () => ({
|
|
16
|
+
stubs: {
|
|
17
|
+
Input: {
|
|
18
|
+
name: 'Input',
|
|
19
|
+
template: '<input class="input-stub" data-testid="input-component" @blur="$emit(\'blur\')" @focus="$emit(\'focus\')" @keyup.enter="$emit(\'keyup\', { key: \'Enter\' })" @keyup.esc="$emit(\'keyup\', { key: \'Escape\' })" />',
|
|
20
|
+
props: ['modelValue', 'reverse', 'borderOnHover'],
|
|
21
|
+
emits: ['blur', 'focus', 'keyup', 'update:modelValue'],
|
|
22
|
+
methods: {
|
|
23
|
+
focus() {},
|
|
24
|
+
blur() {},
|
|
25
|
+
select() {}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
Select: {
|
|
29
|
+
name: 'Select',
|
|
30
|
+
template: '<select class="select-stub" data-testid="select-component"></select>',
|
|
31
|
+
props: ['modelValue', 'items', 'reverse', 'label', 'returnObject', 'borderOnHover'],
|
|
32
|
+
emits: ['blur', 'focus', 'update:model-value'],
|
|
33
|
+
methods: {
|
|
34
|
+
focus() {},
|
|
35
|
+
blur() {}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
DateInput: {
|
|
39
|
+
name: 'DateInput',
|
|
40
|
+
template: '<input class="date-input-stub" data-testid="date-input-component" />',
|
|
41
|
+
props: ['modelValue', 'reverse', 'layout', 'borderOnHover'],
|
|
42
|
+
emits: ['blur', 'focus', 'update:modelValue'],
|
|
43
|
+
methods: {
|
|
44
|
+
focus() {},
|
|
45
|
+
blur() {}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
PhoneInput: {
|
|
49
|
+
name: 'PhoneInput',
|
|
50
|
+
template: '<input class="phone-input-stub" data-testid="phone-input-component" />',
|
|
51
|
+
props: ['modelValue', 'density', 'hideDetails', 'reverse', 'borderOnHover', 'appendInnerIcon'],
|
|
52
|
+
emits: ['blur', 'focus', 'update:phone'],
|
|
53
|
+
methods: {
|
|
54
|
+
focus() {},
|
|
55
|
+
blur() {}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
provide: {
|
|
60
|
+
themeColors: {
|
|
61
|
+
value: {
|
|
62
|
+
'primary-color': '#172774',
|
|
63
|
+
'font_color_title_code': '#333333'
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
vi.clearAllMocks();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('Default Behavior', () => {
|
|
74
|
+
it('renders with required props', () => {
|
|
75
|
+
const wrapper = mount(EditField, {
|
|
76
|
+
props: {
|
|
77
|
+
title: 'Test Field',
|
|
78
|
+
value: 'test value'
|
|
79
|
+
},
|
|
80
|
+
global: createGlobalConfig()
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
84
|
+
expect(wrapper.find('.wl-edit-field').exists()).toBe(true);
|
|
85
|
+
expect(wrapper.find('.title-container').exists()).toBe(true);
|
|
86
|
+
expect(wrapper.find('.input-wrapper').exists()).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('displays title correctly', () => {
|
|
90
|
+
const wrapper = mount(EditField, {
|
|
91
|
+
props: {
|
|
92
|
+
title: 'Field Title',
|
|
93
|
+
value: 'test'
|
|
94
|
+
},
|
|
95
|
+
global: createGlobalConfig()
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(wrapper.find('.title-container p').text()).toBe('Field Title');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('has data-testid for testing', () => {
|
|
102
|
+
const wrapper = mount(EditField, {
|
|
103
|
+
props: {
|
|
104
|
+
title: 'Test',
|
|
105
|
+
value: 'test'
|
|
106
|
+
},
|
|
107
|
+
global: createGlobalConfig()
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('Icon', () => {
|
|
115
|
+
it('displays icon when provided', () => {
|
|
116
|
+
const wrapper = mount(EditField, {
|
|
117
|
+
props: {
|
|
118
|
+
title: 'With Icon',
|
|
119
|
+
value: 'test',
|
|
120
|
+
icon: 'mdi-home'
|
|
121
|
+
},
|
|
122
|
+
global: createGlobalConfig()
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const titleIcon = wrapper.find('.title-container .v-icon');
|
|
126
|
+
expect(titleIcon.exists()).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('hides icon when empty string', () => {
|
|
130
|
+
const wrapper = mount(EditField, {
|
|
131
|
+
props: {
|
|
132
|
+
title: 'No Icon',
|
|
133
|
+
value: 'test',
|
|
134
|
+
icon: ''
|
|
135
|
+
},
|
|
136
|
+
global: createGlobalConfig()
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
expect(wrapper.find('.title-container .v-icon').exists()).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('displays default append icon when not specified', () => {
|
|
143
|
+
const wrapper = mount(EditField, {
|
|
144
|
+
props: {
|
|
145
|
+
title: 'Default Append Icon',
|
|
146
|
+
value: 'test'
|
|
147
|
+
},
|
|
148
|
+
global: createGlobalConfig()
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const appendIcon = wrapper.find('.input-wrapper .v-icon');
|
|
152
|
+
expect(appendIcon.exists()).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('hides append icon when explicitly set to empty string', () => {
|
|
156
|
+
const wrapper = mount(EditField, {
|
|
157
|
+
props: {
|
|
158
|
+
title: 'No Append Icon',
|
|
159
|
+
value: 'test',
|
|
160
|
+
appendIcon: ''
|
|
161
|
+
},
|
|
162
|
+
global: createGlobalConfig()
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// When appendIcon is empty string, computedAppendIcon returns empty string
|
|
166
|
+
// and v-if="computedAppendIcon" evaluates to false
|
|
167
|
+
const appendIcons = wrapper.findAll('.input-wrapper > .v-icon');
|
|
168
|
+
expect(appendIcons.length).toBe(0);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('Input Types', () => {
|
|
173
|
+
it('renders text input when type is "text"', () => {
|
|
174
|
+
const wrapper = mount(EditField, {
|
|
175
|
+
props: {
|
|
176
|
+
title: 'Text Field',
|
|
177
|
+
value: 'text value',
|
|
178
|
+
type: 'text'
|
|
179
|
+
},
|
|
180
|
+
global: createGlobalConfig()
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(wrapper.find('[data-testid="input-component"]').exists()).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('renders select input when type is "select"', () => {
|
|
187
|
+
const items = [
|
|
188
|
+
{ value: 'opt1', title: 'Option 1' },
|
|
189
|
+
{ value: 'opt2', title: 'Option 2' }
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
const wrapper = mount(EditField, {
|
|
193
|
+
props: {
|
|
194
|
+
title: 'Select Field',
|
|
195
|
+
value: 'opt1',
|
|
196
|
+
type: 'select',
|
|
197
|
+
items
|
|
198
|
+
},
|
|
199
|
+
global: createGlobalConfig()
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
expect(wrapper.find('[data-testid="select-component"]').exists()).toBe(true);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('renders date input when type is "date"', () => {
|
|
206
|
+
const wrapper = mount(EditField, {
|
|
207
|
+
props: {
|
|
208
|
+
title: 'Date Field',
|
|
209
|
+
value: '2023-12-25',
|
|
210
|
+
type: 'date'
|
|
211
|
+
},
|
|
212
|
+
global: createGlobalConfig()
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(wrapper.find('[data-testid="date-input-component"]').exists()).toBe(true);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('renders phone input when type is "phone"', () => {
|
|
219
|
+
const wrapper = mount(EditField, {
|
|
220
|
+
props: {
|
|
221
|
+
title: 'Phone Field',
|
|
222
|
+
value: '+1234567890',
|
|
223
|
+
type: 'phone'
|
|
224
|
+
},
|
|
225
|
+
global: createGlobalConfig()
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
expect(wrapper.find('[data-testid="phone-input-component"]').exists()).toBe(true);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('v-model', () => {
|
|
233
|
+
it('updates internalValue when value prop changes', async () => {
|
|
234
|
+
const wrapper = mount(EditField, {
|
|
235
|
+
props: {
|
|
236
|
+
title: 'Test',
|
|
237
|
+
value: 'initial'
|
|
238
|
+
},
|
|
239
|
+
global: createGlobalConfig()
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
await wrapper.setProps({ value: 'updated' });
|
|
243
|
+
await nextTick();
|
|
244
|
+
|
|
245
|
+
// The watcher should have updated internalValue
|
|
246
|
+
expect(wrapper.vm.internalValue).toBe('updated');
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('Editing Functionality', () => {
|
|
251
|
+
it('reverts value on Escape key via onEsc method', () => {
|
|
252
|
+
const wrapper = mount(EditField, {
|
|
253
|
+
props: {
|
|
254
|
+
title: 'Test',
|
|
255
|
+
value: 'original'
|
|
256
|
+
},
|
|
257
|
+
global: createGlobalConfig()
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Simulate changing value
|
|
261
|
+
wrapper.vm.internalValue = 'modified';
|
|
262
|
+
|
|
263
|
+
// Call onEsc
|
|
264
|
+
wrapper.vm.onEsc();
|
|
265
|
+
|
|
266
|
+
expect(wrapper.vm.internalValue).toBe('original');
|
|
267
|
+
expect(wrapper.vm.skipSave).toBe(true);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('calls save on blur when skipSave is false', () => {
|
|
271
|
+
const wrapper = mount(EditField, {
|
|
272
|
+
props: {
|
|
273
|
+
title: 'Test',
|
|
274
|
+
value: 'original'
|
|
275
|
+
},
|
|
276
|
+
global: createGlobalConfig()
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
wrapper.vm.internalValue = 'modified';
|
|
280
|
+
wrapper.vm.skipSave = false;
|
|
281
|
+
|
|
282
|
+
wrapper.vm.handleBlur();
|
|
283
|
+
|
|
284
|
+
expect(wrapper.emitted('update:value')).toBeTruthy();
|
|
285
|
+
expect(wrapper.emitted('update:value')?.[0]).toEqual(['modified']);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('does not save on blur when skipSave is true', () => {
|
|
289
|
+
const wrapper = mount(EditField, {
|
|
290
|
+
props: {
|
|
291
|
+
title: 'Test',
|
|
292
|
+
value: 'original'
|
|
293
|
+
},
|
|
294
|
+
global: createGlobalConfig()
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
wrapper.vm.internalValue = 'modified';
|
|
298
|
+
wrapper.vm.skipSave = true;
|
|
299
|
+
|
|
300
|
+
wrapper.vm.handleBlur();
|
|
301
|
+
|
|
302
|
+
expect(wrapper.emitted('update:value')).toBeFalsy();
|
|
303
|
+
expect(wrapper.vm.skipSave).toBe(false);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('does not emit update:value when internalValue equals prop value', () => {
|
|
307
|
+
const wrapper = mount(EditField, {
|
|
308
|
+
props: {
|
|
309
|
+
title: 'Test',
|
|
310
|
+
value: 'same'
|
|
311
|
+
},
|
|
312
|
+
global: createGlobalConfig()
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
wrapper.vm.internalValue = 'same';
|
|
316
|
+
wrapper.vm.save();
|
|
317
|
+
|
|
318
|
+
expect(wrapper.emitted('update:value')).toBeFalsy();
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
describe('Slot Functionality', () => {
|
|
323
|
+
it('renders input slot when provided', () => {
|
|
324
|
+
const wrapper = mount(EditField, {
|
|
325
|
+
props: {
|
|
326
|
+
title: 'Custom Slot',
|
|
327
|
+
value: 'test'
|
|
328
|
+
},
|
|
329
|
+
global: createGlobalConfig(),
|
|
330
|
+
slots: {
|
|
331
|
+
input: '<div data-testid="custom-input">Custom Input Content</div>'
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
expect(wrapper.find('[data-testid="custom-input"]').exists()).toBe(true);
|
|
336
|
+
expect(wrapper.find('[data-testid="custom-input"]').text()).toBe('Custom Input Content');
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('passes isInputFocused to input slot', () => {
|
|
340
|
+
const wrapper = mount(EditField, {
|
|
341
|
+
props: {
|
|
342
|
+
title: 'Slot Props',
|
|
343
|
+
value: 'test'
|
|
344
|
+
},
|
|
345
|
+
global: createGlobalConfig(),
|
|
346
|
+
slots: {
|
|
347
|
+
input: `
|
|
348
|
+
<template #input="{ isInputFocused }">
|
|
349
|
+
<div data-testid="focused-indicator">
|
|
350
|
+
Focused: {{ isInputFocused }}
|
|
351
|
+
</div>
|
|
352
|
+
</template>
|
|
353
|
+
`
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const indicator = wrapper.find('[data-testid="focused-indicator"]');
|
|
358
|
+
expect(indicator.exists()).toBe(true);
|
|
359
|
+
expect(indicator.text()).toContain('Focused: false');
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
describe('Accessibility', () => {
|
|
364
|
+
it('has proper tabindex on title container', () => {
|
|
365
|
+
const wrapper = mount(EditField, {
|
|
366
|
+
props: {
|
|
367
|
+
title: 'Accessibility Test',
|
|
368
|
+
value: 'test'
|
|
369
|
+
},
|
|
370
|
+
global: createGlobalConfig()
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
const titleContainer = wrapper.find('.title-container');
|
|
374
|
+
expect(titleContainer.attributes('tabindex')).toBe('-1');
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('provides meaningful title text', () => {
|
|
378
|
+
const wrapper = mount(EditField, {
|
|
379
|
+
props: {
|
|
380
|
+
title: 'User Email Address',
|
|
381
|
+
value: 'user@example.com'
|
|
382
|
+
},
|
|
383
|
+
global: createGlobalConfig()
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
expect(wrapper.find('.title-container p').text()).toBe('User Email Address');
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
describe('Edge Cases', () => {
|
|
391
|
+
it('handles empty string values', () => {
|
|
392
|
+
const wrapper = mount(EditField, {
|
|
393
|
+
props: {
|
|
394
|
+
title: 'Empty Value',
|
|
395
|
+
value: ''
|
|
396
|
+
},
|
|
397
|
+
global: createGlobalConfig()
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
expect(wrapper.vm.internalValue).toBe('');
|
|
401
|
+
|
|
402
|
+
wrapper.vm.internalValue = 'new value';
|
|
403
|
+
wrapper.vm.save();
|
|
404
|
+
|
|
405
|
+
expect(wrapper.emitted('update:value')?.[0]).toEqual(['new value']);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('handles special characters in values', () => {
|
|
409
|
+
const specialValue = '!@#$%^&*()_+{}|:"<>?`~[]\\;\',./ ñáéíóú';
|
|
410
|
+
const wrapper = mount(EditField, {
|
|
411
|
+
props: {
|
|
412
|
+
title: 'Special Chars',
|
|
413
|
+
value: specialValue
|
|
414
|
+
},
|
|
415
|
+
global: createGlobalConfig()
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
expect(wrapper.vm.internalValue).toBe(specialValue);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('handles number values', () => {
|
|
422
|
+
const wrapper = mount(EditField, {
|
|
423
|
+
props: {
|
|
424
|
+
title: 'Number Value',
|
|
425
|
+
value: 42
|
|
426
|
+
},
|
|
427
|
+
global: createGlobalConfig()
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
expect(wrapper.vm.internalValue).toBe(42);
|
|
431
|
+
|
|
432
|
+
wrapper.vm.internalValue = 100;
|
|
433
|
+
wrapper.vm.save();
|
|
434
|
+
|
|
435
|
+
expect(wrapper.emitted('update:value')?.[0]).toEqual([100]);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it('handles boolean values', () => {
|
|
439
|
+
const wrapper = mount(EditField, {
|
|
440
|
+
props: {
|
|
441
|
+
title: 'Boolean Value',
|
|
442
|
+
value: true
|
|
443
|
+
},
|
|
444
|
+
global: createGlobalConfig()
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
expect(wrapper.vm.internalValue).toBe(true);
|
|
448
|
+
|
|
449
|
+
wrapper.vm.internalValue = false;
|
|
450
|
+
wrapper.vm.save();
|
|
451
|
+
|
|
452
|
+
expect(wrapper.emitted('update:value')?.[0]).toEqual([false]);
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
describe('Complex Scenarios', () => {
|
|
457
|
+
it('handles escape during editing', () => {
|
|
458
|
+
const wrapper = mount(EditField, {
|
|
459
|
+
props: {
|
|
460
|
+
title: 'Escape Test',
|
|
461
|
+
value: 'original'
|
|
462
|
+
},
|
|
463
|
+
global: createGlobalConfig()
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// Start editing and change value
|
|
467
|
+
wrapper.vm.internalValue = 'modified';
|
|
468
|
+
|
|
469
|
+
// Press escape
|
|
470
|
+
wrapper.vm.onEsc();
|
|
471
|
+
|
|
472
|
+
expect(wrapper.vm.internalValue).toBe('original');
|
|
473
|
+
expect(wrapper.vm.skipSave).toBe(true);
|
|
474
|
+
|
|
475
|
+
// Simulate blur after escape
|
|
476
|
+
wrapper.vm.handleBlur();
|
|
477
|
+
|
|
478
|
+
// Should not emit update because skipSave was true
|
|
479
|
+
expect(wrapper.emitted('update:value')).toBeFalsy();
|
|
480
|
+
expect(wrapper.vm.skipSave).toBe(false);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('works with select type and items', () => {
|
|
484
|
+
const items = [
|
|
485
|
+
{ value: 'opt1', title: 'Option 1' },
|
|
486
|
+
{ value: 'opt2', title: 'Option 2' },
|
|
487
|
+
{ value: 'opt3', title: 'Option 3' }
|
|
488
|
+
];
|
|
489
|
+
|
|
490
|
+
const wrapper = mount(EditField, {
|
|
491
|
+
props: {
|
|
492
|
+
title: 'Select Test',
|
|
493
|
+
value: 'opt1',
|
|
494
|
+
type: 'select',
|
|
495
|
+
items
|
|
496
|
+
},
|
|
497
|
+
global: createGlobalConfig()
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
expect(wrapper.find('[data-testid="select-component"]').exists()).toBe(true);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it('passes inputProps to child components', () => {
|
|
504
|
+
const inputProps = {
|
|
505
|
+
placeholder: 'Enter text here',
|
|
506
|
+
disabled: false
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const wrapper = mount(EditField, {
|
|
510
|
+
props: {
|
|
511
|
+
title: 'Props Test',
|
|
512
|
+
value: 'test',
|
|
513
|
+
type: 'text',
|
|
514
|
+
inputProps
|
|
515
|
+
},
|
|
516
|
+
global: createGlobalConfig()
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
expect(wrapper.find('[data-testid="input-component"]').exists()).toBe(true);
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
describe('Append Icon Click', () => {
|
|
524
|
+
it('clicking append icon triggers onStartEditing', async () => {
|
|
525
|
+
const wrapper = mount(EditField, {
|
|
526
|
+
props: {
|
|
527
|
+
title: 'Test',
|
|
528
|
+
value: 'test'
|
|
529
|
+
},
|
|
530
|
+
global: createGlobalConfig()
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const appendIcon = wrapper.find('.input-wrapper > .v-icon');
|
|
534
|
+
expect(appendIcon.exists()).toBe(true);
|
|
535
|
+
|
|
536
|
+
// The onStartEditing calls inputElementRef?.focus?.()
|
|
537
|
+
// Since we're using stubs, we verify the click is handled
|
|
538
|
+
await appendIcon.trigger('click');
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
});
|