@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,436 +1,436 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { mount, VueWrapper } from '@vue/test-utils';
|
|
3
|
-
import Actions from '../../../../../src/components/Appointment/Card/Actions.vue';
|
|
4
|
-
import type { AppointmentData } from '../../../../../src/types';
|
|
5
|
-
|
|
6
|
-
// Mock vue-i18n
|
|
7
|
-
vi.mock('vue-i18n', () => ({
|
|
8
|
-
useI18n: () => ({
|
|
9
|
-
t: (key: string) => {
|
|
10
|
-
const translations: Record<string, string> = {
|
|
11
|
-
'wl.appointment_card.cancel': 'Termin stornieren',
|
|
12
|
-
'wl.appointment_card.reschedule': 'Termin verschieben',
|
|
13
|
-
'wl.appointment_card.confirm': 'Termin bestätigen',
|
|
14
|
-
'wl.appointment_card.reschedule_after_cancellation': 'Diesen Termin nochmal buchen',
|
|
15
|
-
'appointments.status.cancelled': 'Termin storniert',
|
|
16
|
-
'appointments.status.missed': 'Termin verpasst',
|
|
17
|
-
'appointment.missed.title': 'Termin verpasst',
|
|
18
|
-
'phone': 'Telefon',
|
|
19
|
-
'email': 'E-Mail'
|
|
20
|
-
};
|
|
21
|
-
return translations[key] || key;
|
|
22
|
-
}
|
|
23
|
-
})
|
|
24
|
-
}));
|
|
25
|
-
|
|
26
|
-
describe('Appointment Card Actions', () => {
|
|
27
|
-
let wrapper: VueWrapper;
|
|
28
|
-
|
|
29
|
-
const createMockAppointment = (overrides: Partial<AppointmentData> = {}): AppointmentData => ({
|
|
30
|
-
id: '123',
|
|
31
|
-
template_name: 'Test Template',
|
|
32
|
-
description: 'Test Description',
|
|
33
|
-
dentist: {
|
|
34
|
-
name: 'Dr. Test',
|
|
35
|
-
gender: 'Male',
|
|
36
|
-
imageSrc: 'test.jpg'
|
|
37
|
-
},
|
|
38
|
-
start: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), // Tomorrow
|
|
39
|
-
type: 1,
|
|
40
|
-
status: 'upcoming',
|
|
41
|
-
is_confirmed: false,
|
|
42
|
-
...overrides
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const createGlobalConfig = () => ({
|
|
46
|
-
stubs: {
|
|
47
|
-
Button: {
|
|
48
|
-
template: '<button class="button-stub" :class="{ \'action-button\': true }" @click="$emit(\'click\')">{{ label }}</button>',
|
|
49
|
-
props: ['variant', 'prependIcon', 'size', 'label', 'readonly', 'disabled', 'color'],
|
|
50
|
-
emits: ['click']
|
|
51
|
-
},
|
|
52
|
-
'v-icon': {
|
|
53
|
-
template: '<span class="v-icon-stub"></span>',
|
|
54
|
-
props: ['size', 'icon']
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
beforeEach(() => {
|
|
60
|
-
vi.clearAllMocks();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe('Upcoming Appointments', () => {
|
|
64
|
-
beforeEach(() => {
|
|
65
|
-
const appointment = createMockAppointment({ status: 'upcoming' });
|
|
66
|
-
wrapper = mount(Actions, {
|
|
67
|
-
props: { appointment },
|
|
68
|
-
global: createGlobalConfig()
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should render cancel and reschedule buttons for upcoming appointments', () => {
|
|
73
|
-
expect(wrapper.html()).toContain('Termin stornieren');
|
|
74
|
-
expect(wrapper.html()).toContain('Termin verschieben');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('should have actions grid structure for upcoming appointments', () => {
|
|
78
|
-
expect(wrapper.find('.actions-grid').exists()).toBe(true);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('should emit cancelled event when cancel button is clicked', async () => {
|
|
82
|
-
const buttons = wrapper.findAll('.button-stub');
|
|
83
|
-
const cancelButton = buttons.find(b => b.text().includes('stornieren'));
|
|
84
|
-
|
|
85
|
-
if (cancelButton) {
|
|
86
|
-
await cancelButton.trigger('click');
|
|
87
|
-
expect(wrapper.emitted('cancelled')).toBeTruthy();
|
|
88
|
-
expect(wrapper.emitted('cancelled')?.[0]).toEqual(['123']);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should emit rescheduled event when reschedule button is clicked', async () => {
|
|
93
|
-
const buttons = wrapper.findAll('.button-stub');
|
|
94
|
-
const rescheduleButton = buttons.find(b => b.text().includes('verschieben'));
|
|
95
|
-
|
|
96
|
-
if (rescheduleButton) {
|
|
97
|
-
await rescheduleButton.trigger('click');
|
|
98
|
-
expect(wrapper.emitted('rescheduled')).toBeTruthy();
|
|
99
|
-
expect(wrapper.emitted('rescheduled')?.[0]).toEqual(['123']);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
describe('Cancelled Appointments', () => {
|
|
105
|
-
beforeEach(() => {
|
|
106
|
-
const appointment = createMockAppointment({ status: 'cancelled' });
|
|
107
|
-
wrapper = mount(Actions, {
|
|
108
|
-
props: { appointment },
|
|
109
|
-
global: createGlobalConfig()
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('should render cancelled status text for cancelled appointments', () => {
|
|
114
|
-
expect(wrapper.html()).toContain('Termin storniert');
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('should render reschedule button for cancelled appointments', () => {
|
|
118
|
-
expect(wrapper.html()).toContain('Diesen Termin nochmal buchen');
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it('should have actions grid for cancelled appointments', () => {
|
|
122
|
-
expect(wrapper.find('.actions-grid').exists()).toBe(true);
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
describe('Done Appointments', () => {
|
|
127
|
-
it('should render rebook button for done appointments with for_patient flag', () => {
|
|
128
|
-
const appointment = createMockAppointment({
|
|
129
|
-
status: 'done',
|
|
130
|
-
for_patient: true
|
|
131
|
-
} as any);
|
|
132
|
-
|
|
133
|
-
wrapper = mount(Actions, {
|
|
134
|
-
props: { appointment },
|
|
135
|
-
global: createGlobalConfig()
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
expect(wrapper.html()).toContain('Diesen Termin nochmal buchen');
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('should not render anything for done appointments without for_patient flag', () => {
|
|
142
|
-
const appointment = createMockAppointment({
|
|
143
|
-
status: 'done',
|
|
144
|
-
for_patient: false
|
|
145
|
-
} as any);
|
|
146
|
-
|
|
147
|
-
wrapper = mount(Actions, {
|
|
148
|
-
props: { appointment },
|
|
149
|
-
global: createGlobalConfig()
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
expect(wrapper.find('.actions-grid').exists()).toBe(false);
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
describe('Missed Appointments', () => {
|
|
157
|
-
beforeEach(() => {
|
|
158
|
-
const appointment = createMockAppointment({ status: 'missed' } as any);
|
|
159
|
-
wrapper = mount(Actions, {
|
|
160
|
-
props: { appointment },
|
|
161
|
-
global: createGlobalConfig()
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('should render missed status text', () => {
|
|
166
|
-
expect(wrapper.html()).toContain('Termin verpasst');
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('should render contact information', () => {
|
|
170
|
-
expect(wrapper.html()).toContain('Telefon');
|
|
171
|
-
expect(wrapper.html()).toContain('E-Mail');
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it('should have missed info box', () => {
|
|
175
|
-
expect(wrapper.find('.missed-info-box').exists()).toBe(true);
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
describe('Confirmable Logic', () => {
|
|
180
|
-
it('should show confirm button when appointment is confirmable', async () => {
|
|
181
|
-
// Create appointment within 48h and unconfirmed
|
|
182
|
-
const futureDate = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString(); // 12 hours from now
|
|
183
|
-
const confirmableAppointment = createMockAppointment({
|
|
184
|
-
start: futureDate,
|
|
185
|
-
is_confirmed: false,
|
|
186
|
-
status: 'upcoming'
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
wrapper = mount(Actions, {
|
|
190
|
-
props: { appointment: confirmableAppointment },
|
|
191
|
-
global: createGlobalConfig()
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
expect(wrapper.html()).toContain('Termin bestätigen');
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('should not show confirm button when appointment is already confirmed', async () => {
|
|
198
|
-
const futureDate = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString();
|
|
199
|
-
const confirmedAppointment = createMockAppointment({
|
|
200
|
-
start: futureDate,
|
|
201
|
-
is_confirmed: true,
|
|
202
|
-
status: 'upcoming'
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
wrapper = mount(Actions, {
|
|
206
|
-
props: { appointment: confirmedAppointment },
|
|
207
|
-
global: createGlobalConfig()
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
expect(wrapper.html()).not.toContain('Termin bestätigen');
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('should not show confirm button when appointment is more than 48h away', async () => {
|
|
214
|
-
const distantFuture = new Date(Date.now() + 72 * 60 * 60 * 1000).toISOString(); // 72 hours from now
|
|
215
|
-
const distantAppointment = createMockAppointment({
|
|
216
|
-
start: distantFuture,
|
|
217
|
-
is_confirmed: false,
|
|
218
|
-
status: 'upcoming'
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
wrapper = mount(Actions, {
|
|
222
|
-
props: { appointment: distantAppointment },
|
|
223
|
-
global: createGlobalConfig()
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
expect(wrapper.html()).not.toContain('Termin bestätigen');
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it('should emit confirmed event when confirm button is clicked', async () => {
|
|
230
|
-
const futureDate = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString();
|
|
231
|
-
const confirmableAppointment = createMockAppointment({
|
|
232
|
-
start: futureDate,
|
|
233
|
-
is_confirmed: false,
|
|
234
|
-
status: 'upcoming'
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
wrapper = mount(Actions, {
|
|
238
|
-
props: { appointment: confirmableAppointment },
|
|
239
|
-
global: createGlobalConfig()
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
const confirmButton = wrapper.find('.full-width-button .button-stub');
|
|
243
|
-
if (confirmButton.exists()) {
|
|
244
|
-
await confirmButton.trigger('click');
|
|
245
|
-
expect(wrapper.emitted('confirmed')).toBeTruthy();
|
|
246
|
-
expect(wrapper.emitted('confirmed')?.[0]).toEqual(['123']);
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
describe('CSS Classes and Styling', () => {
|
|
252
|
-
beforeEach(() => {
|
|
253
|
-
const appointment = createMockAppointment({ status: 'upcoming' });
|
|
254
|
-
wrapper = mount(Actions, {
|
|
255
|
-
props: { appointment },
|
|
256
|
-
global: createGlobalConfig()
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
it('should apply actions-grid class', () => {
|
|
261
|
-
expect(wrapper.find('.actions-grid').exists()).toBe(true);
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
it('should apply action-button class to buttons', () => {
|
|
265
|
-
const actionButtons = wrapper.findAll('.action-button');
|
|
266
|
-
expect(actionButtons.length).toBeGreaterThan(0);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('should have full-width button class when confirmable', async () => {
|
|
270
|
-
const futureDate = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString();
|
|
271
|
-
const confirmableAppointment = createMockAppointment({
|
|
272
|
-
start: futureDate,
|
|
273
|
-
is_confirmed: false,
|
|
274
|
-
status: 'upcoming'
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
wrapper = mount(Actions, {
|
|
278
|
-
props: { appointment: confirmableAppointment },
|
|
279
|
-
global: createGlobalConfig()
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
expect(wrapper.find('.full-width-button').exists()).toBe(true);
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
describe('Component Template Structure', () => {
|
|
287
|
-
it('should render different templates based on status', () => {
|
|
288
|
-
// Test upcoming
|
|
289
|
-
const upcomingAppointment = createMockAppointment({ status: 'upcoming' });
|
|
290
|
-
wrapper = mount(Actions, {
|
|
291
|
-
props: { appointment: upcomingAppointment },
|
|
292
|
-
global: createGlobalConfig()
|
|
293
|
-
});
|
|
294
|
-
expect(wrapper.html()).toContain('Termin stornieren');
|
|
295
|
-
|
|
296
|
-
// Test cancelled
|
|
297
|
-
const cancelledAppointment = createMockAppointment({ status: 'cancelled' });
|
|
298
|
-
wrapper = mount(Actions, {
|
|
299
|
-
props: { appointment: cancelledAppointment },
|
|
300
|
-
global: createGlobalConfig()
|
|
301
|
-
});
|
|
302
|
-
expect(wrapper.html()).toContain('Termin storniert');
|
|
303
|
-
|
|
304
|
-
// Test missed
|
|
305
|
-
const missedAppointment = createMockAppointment({ status: 'missed' } as any);
|
|
306
|
-
wrapper = mount(Actions, {
|
|
307
|
-
props: { appointment: missedAppointment },
|
|
308
|
-
global: createGlobalConfig()
|
|
309
|
-
});
|
|
310
|
-
expect(wrapper.html()).toContain('Termin verpasst');
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
describe('Props Validation', () => {
|
|
315
|
-
it('should accept appointment prop', () => {
|
|
316
|
-
const appointment = createMockAppointment();
|
|
317
|
-
wrapper = mount(Actions, {
|
|
318
|
-
props: { appointment },
|
|
319
|
-
global: createGlobalConfig()
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
expect(wrapper.props('appointment')).toEqual(appointment);
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
it('should handle appointment prop changes', async () => {
|
|
326
|
-
const appointment = createMockAppointment({ status: 'upcoming' });
|
|
327
|
-
wrapper = mount(Actions, {
|
|
328
|
-
props: { appointment },
|
|
329
|
-
global: createGlobalConfig()
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
expect(wrapper.html()).toContain('Termin stornieren');
|
|
333
|
-
|
|
334
|
-
const cancelledAppointment = createMockAppointment({ status: 'cancelled' });
|
|
335
|
-
await wrapper.setProps({ appointment: cancelledAppointment });
|
|
336
|
-
|
|
337
|
-
expect(wrapper.html()).toContain('Termin storniert');
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
it('should accept disabled prop', () => {
|
|
341
|
-
const appointment = createMockAppointment({ status: 'upcoming' });
|
|
342
|
-
wrapper = mount(Actions, {
|
|
343
|
-
props: { appointment, disabled: true },
|
|
344
|
-
global: createGlobalConfig()
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
expect(wrapper.props('disabled')).toBe(true);
|
|
348
|
-
});
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
describe('Edge Cases', () => {
|
|
352
|
-
it('should handle appointment with null start gracefully', () => {
|
|
353
|
-
const appointmentWithNulls = createMockAppointment({
|
|
354
|
-
start: null as any,
|
|
355
|
-
is_confirmed: null as any
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
wrapper = mount(Actions, {
|
|
359
|
-
props: { appointment: appointmentWithNulls },
|
|
360
|
-
global: createGlobalConfig()
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
expect(wrapper.exists()).toBe(true);
|
|
364
|
-
});
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
describe('Accessibility', () => {
|
|
368
|
-
beforeEach(() => {
|
|
369
|
-
const appointment = createMockAppointment({ status: 'upcoming' });
|
|
370
|
-
wrapper = mount(Actions, {
|
|
371
|
-
props: { appointment },
|
|
372
|
-
global: createGlobalConfig()
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
it('should have accessible button labels', () => {
|
|
377
|
-
const buttons = wrapper.findAll('.button-stub');
|
|
378
|
-
expect(buttons.length).toBeGreaterThan(0);
|
|
379
|
-
|
|
380
|
-
buttons.forEach(button => {
|
|
381
|
-
const textContent = button.text();
|
|
382
|
-
expect(textContent).toBeTruthy();
|
|
383
|
-
});
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
it('should provide semantic meaning for action buttons', () => {
|
|
387
|
-
const actionGrid = wrapper.find('.actions-grid');
|
|
388
|
-
expect(actionGrid.exists()).toBe(true);
|
|
389
|
-
|
|
390
|
-
// Check that buttons have meaningful text content
|
|
391
|
-
expect(wrapper.html()).toContain('Termin stornieren');
|
|
392
|
-
expect(wrapper.html()).toContain('Termin verschieben');
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
it('should have proper semantic structure for confirm button', async () => {
|
|
396
|
-
const futureDate = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString();
|
|
397
|
-
const confirmableAppointment = createMockAppointment({
|
|
398
|
-
start: futureDate,
|
|
399
|
-
is_confirmed: false,
|
|
400
|
-
status: 'upcoming'
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
wrapper = mount(Actions, {
|
|
404
|
-
props: { appointment: confirmableAppointment },
|
|
405
|
-
global: createGlobalConfig()
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
const confirmButton = wrapper.find('.full-width-button');
|
|
409
|
-
expect(confirmButton.exists()).toBe(true);
|
|
410
|
-
expect(wrapper.html()).toContain('Termin bestätigen');
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
it('should use appropriate structure for different appointment states', async () => {
|
|
414
|
-
// Test upcoming appointment has action grid
|
|
415
|
-
expect(wrapper.find('.actions-grid').exists()).toBe(true);
|
|
416
|
-
|
|
417
|
-
// Test cancelled appointment
|
|
418
|
-
const cancelledAppointment = createMockAppointment({ status: 'cancelled' });
|
|
419
|
-
await wrapper.setProps({ appointment: cancelledAppointment });
|
|
420
|
-
expect(wrapper.html()).toContain('Termin storniert');
|
|
421
|
-
|
|
422
|
-
// Test missed appointment has info box
|
|
423
|
-
const missedAppointment = createMockAppointment({ status: 'missed' } as any);
|
|
424
|
-
await wrapper.setProps({ appointment: missedAppointment });
|
|
425
|
-
expect(wrapper.find('.missed-info-box').exists()).toBe(true);
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
it('should have clear visual hierarchy with CSS classes', () => {
|
|
429
|
-
expect(wrapper.find('.actions-grid').exists()).toBe(true);
|
|
430
|
-
|
|
431
|
-
// Actions should be in a logical visual group
|
|
432
|
-
const actionButtons = wrapper.findAll('.action-button');
|
|
433
|
-
expect(actionButtons.length).toBeGreaterThan(0);
|
|
434
|
-
});
|
|
435
|
-
});
|
|
436
|
-
});
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { mount, VueWrapper } from '@vue/test-utils';
|
|
3
|
+
import Actions from '../../../../../src/components/Appointment/Card/Actions.vue';
|
|
4
|
+
import type { AppointmentData } from '../../../../../src/types';
|
|
5
|
+
|
|
6
|
+
// Mock vue-i18n
|
|
7
|
+
vi.mock('vue-i18n', () => ({
|
|
8
|
+
useI18n: () => ({
|
|
9
|
+
t: (key: string) => {
|
|
10
|
+
const translations: Record<string, string> = {
|
|
11
|
+
'wl.appointment_card.cancel': 'Termin stornieren',
|
|
12
|
+
'wl.appointment_card.reschedule': 'Termin verschieben',
|
|
13
|
+
'wl.appointment_card.confirm': 'Termin bestätigen',
|
|
14
|
+
'wl.appointment_card.reschedule_after_cancellation': 'Diesen Termin nochmal buchen',
|
|
15
|
+
'appointments.status.cancelled': 'Termin storniert',
|
|
16
|
+
'appointments.status.missed': 'Termin verpasst',
|
|
17
|
+
'appointment.missed.title': 'Termin verpasst',
|
|
18
|
+
'phone': 'Telefon',
|
|
19
|
+
'email': 'E-Mail'
|
|
20
|
+
};
|
|
21
|
+
return translations[key] || key;
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
describe('Appointment Card Actions', () => {
|
|
27
|
+
let wrapper: VueWrapper;
|
|
28
|
+
|
|
29
|
+
const createMockAppointment = (overrides: Partial<AppointmentData> = {}): AppointmentData => ({
|
|
30
|
+
id: '123',
|
|
31
|
+
template_name: 'Test Template',
|
|
32
|
+
description: 'Test Description',
|
|
33
|
+
dentist: {
|
|
34
|
+
name: 'Dr. Test',
|
|
35
|
+
gender: 'Male',
|
|
36
|
+
imageSrc: 'test.jpg'
|
|
37
|
+
},
|
|
38
|
+
start: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), // Tomorrow
|
|
39
|
+
type: 1,
|
|
40
|
+
status: 'upcoming',
|
|
41
|
+
is_confirmed: false,
|
|
42
|
+
...overrides
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const createGlobalConfig = () => ({
|
|
46
|
+
stubs: {
|
|
47
|
+
Button: {
|
|
48
|
+
template: '<button class="button-stub" :class="{ \'action-button\': true }" @click="$emit(\'click\')">{{ label }}</button>',
|
|
49
|
+
props: ['variant', 'prependIcon', 'size', 'label', 'readonly', 'disabled', 'color'],
|
|
50
|
+
emits: ['click']
|
|
51
|
+
},
|
|
52
|
+
'v-icon': {
|
|
53
|
+
template: '<span class="v-icon-stub"></span>',
|
|
54
|
+
props: ['size', 'icon']
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
vi.clearAllMocks();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('Upcoming Appointments', () => {
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
const appointment = createMockAppointment({ status: 'upcoming' });
|
|
66
|
+
wrapper = mount(Actions, {
|
|
67
|
+
props: { appointment },
|
|
68
|
+
global: createGlobalConfig()
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should render cancel and reschedule buttons for upcoming appointments', () => {
|
|
73
|
+
expect(wrapper.html()).toContain('Termin stornieren');
|
|
74
|
+
expect(wrapper.html()).toContain('Termin verschieben');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should have actions grid structure for upcoming appointments', () => {
|
|
78
|
+
expect(wrapper.find('.actions-grid').exists()).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should emit cancelled event when cancel button is clicked', async () => {
|
|
82
|
+
const buttons = wrapper.findAll('.button-stub');
|
|
83
|
+
const cancelButton = buttons.find(b => b.text().includes('stornieren'));
|
|
84
|
+
|
|
85
|
+
if (cancelButton) {
|
|
86
|
+
await cancelButton.trigger('click');
|
|
87
|
+
expect(wrapper.emitted('cancelled')).toBeTruthy();
|
|
88
|
+
expect(wrapper.emitted('cancelled')?.[0]).toEqual(['123']);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should emit rescheduled event when reschedule button is clicked', async () => {
|
|
93
|
+
const buttons = wrapper.findAll('.button-stub');
|
|
94
|
+
const rescheduleButton = buttons.find(b => b.text().includes('verschieben'));
|
|
95
|
+
|
|
96
|
+
if (rescheduleButton) {
|
|
97
|
+
await rescheduleButton.trigger('click');
|
|
98
|
+
expect(wrapper.emitted('rescheduled')).toBeTruthy();
|
|
99
|
+
expect(wrapper.emitted('rescheduled')?.[0]).toEqual(['123']);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('Cancelled Appointments', () => {
|
|
105
|
+
beforeEach(() => {
|
|
106
|
+
const appointment = createMockAppointment({ status: 'cancelled' });
|
|
107
|
+
wrapper = mount(Actions, {
|
|
108
|
+
props: { appointment },
|
|
109
|
+
global: createGlobalConfig()
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should render cancelled status text for cancelled appointments', () => {
|
|
114
|
+
expect(wrapper.html()).toContain('Termin storniert');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should render reschedule button for cancelled appointments', () => {
|
|
118
|
+
expect(wrapper.html()).toContain('Diesen Termin nochmal buchen');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should have actions grid for cancelled appointments', () => {
|
|
122
|
+
expect(wrapper.find('.actions-grid').exists()).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('Done Appointments', () => {
|
|
127
|
+
it('should render rebook button for done appointments with for_patient flag', () => {
|
|
128
|
+
const appointment = createMockAppointment({
|
|
129
|
+
status: 'done',
|
|
130
|
+
for_patient: true
|
|
131
|
+
} as any);
|
|
132
|
+
|
|
133
|
+
wrapper = mount(Actions, {
|
|
134
|
+
props: { appointment },
|
|
135
|
+
global: createGlobalConfig()
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(wrapper.html()).toContain('Diesen Termin nochmal buchen');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should not render anything for done appointments without for_patient flag', () => {
|
|
142
|
+
const appointment = createMockAppointment({
|
|
143
|
+
status: 'done',
|
|
144
|
+
for_patient: false
|
|
145
|
+
} as any);
|
|
146
|
+
|
|
147
|
+
wrapper = mount(Actions, {
|
|
148
|
+
props: { appointment },
|
|
149
|
+
global: createGlobalConfig()
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(wrapper.find('.actions-grid').exists()).toBe(false);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('Missed Appointments', () => {
|
|
157
|
+
beforeEach(() => {
|
|
158
|
+
const appointment = createMockAppointment({ status: 'missed' } as any);
|
|
159
|
+
wrapper = mount(Actions, {
|
|
160
|
+
props: { appointment },
|
|
161
|
+
global: createGlobalConfig()
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should render missed status text', () => {
|
|
166
|
+
expect(wrapper.html()).toContain('Termin verpasst');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should render contact information', () => {
|
|
170
|
+
expect(wrapper.html()).toContain('Telefon');
|
|
171
|
+
expect(wrapper.html()).toContain('E-Mail');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should have missed info box', () => {
|
|
175
|
+
expect(wrapper.find('.missed-info-box').exists()).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('Confirmable Logic', () => {
|
|
180
|
+
it('should show confirm button when appointment is confirmable', async () => {
|
|
181
|
+
// Create appointment within 48h and unconfirmed
|
|
182
|
+
const futureDate = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString(); // 12 hours from now
|
|
183
|
+
const confirmableAppointment = createMockAppointment({
|
|
184
|
+
start: futureDate,
|
|
185
|
+
is_confirmed: false,
|
|
186
|
+
status: 'upcoming'
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
wrapper = mount(Actions, {
|
|
190
|
+
props: { appointment: confirmableAppointment },
|
|
191
|
+
global: createGlobalConfig()
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
expect(wrapper.html()).toContain('Termin bestätigen');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should not show confirm button when appointment is already confirmed', async () => {
|
|
198
|
+
const futureDate = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString();
|
|
199
|
+
const confirmedAppointment = createMockAppointment({
|
|
200
|
+
start: futureDate,
|
|
201
|
+
is_confirmed: true,
|
|
202
|
+
status: 'upcoming'
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
wrapper = mount(Actions, {
|
|
206
|
+
props: { appointment: confirmedAppointment },
|
|
207
|
+
global: createGlobalConfig()
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
expect(wrapper.html()).not.toContain('Termin bestätigen');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should not show confirm button when appointment is more than 48h away', async () => {
|
|
214
|
+
const distantFuture = new Date(Date.now() + 72 * 60 * 60 * 1000).toISOString(); // 72 hours from now
|
|
215
|
+
const distantAppointment = createMockAppointment({
|
|
216
|
+
start: distantFuture,
|
|
217
|
+
is_confirmed: false,
|
|
218
|
+
status: 'upcoming'
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
wrapper = mount(Actions, {
|
|
222
|
+
props: { appointment: distantAppointment },
|
|
223
|
+
global: createGlobalConfig()
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
expect(wrapper.html()).not.toContain('Termin bestätigen');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should emit confirmed event when confirm button is clicked', async () => {
|
|
230
|
+
const futureDate = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString();
|
|
231
|
+
const confirmableAppointment = createMockAppointment({
|
|
232
|
+
start: futureDate,
|
|
233
|
+
is_confirmed: false,
|
|
234
|
+
status: 'upcoming'
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
wrapper = mount(Actions, {
|
|
238
|
+
props: { appointment: confirmableAppointment },
|
|
239
|
+
global: createGlobalConfig()
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const confirmButton = wrapper.find('.full-width-button .button-stub');
|
|
243
|
+
if (confirmButton.exists()) {
|
|
244
|
+
await confirmButton.trigger('click');
|
|
245
|
+
expect(wrapper.emitted('confirmed')).toBeTruthy();
|
|
246
|
+
expect(wrapper.emitted('confirmed')?.[0]).toEqual(['123']);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe('CSS Classes and Styling', () => {
|
|
252
|
+
beforeEach(() => {
|
|
253
|
+
const appointment = createMockAppointment({ status: 'upcoming' });
|
|
254
|
+
wrapper = mount(Actions, {
|
|
255
|
+
props: { appointment },
|
|
256
|
+
global: createGlobalConfig()
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should apply actions-grid class', () => {
|
|
261
|
+
expect(wrapper.find('.actions-grid').exists()).toBe(true);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should apply action-button class to buttons', () => {
|
|
265
|
+
const actionButtons = wrapper.findAll('.action-button');
|
|
266
|
+
expect(actionButtons.length).toBeGreaterThan(0);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should have full-width button class when confirmable', async () => {
|
|
270
|
+
const futureDate = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString();
|
|
271
|
+
const confirmableAppointment = createMockAppointment({
|
|
272
|
+
start: futureDate,
|
|
273
|
+
is_confirmed: false,
|
|
274
|
+
status: 'upcoming'
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
wrapper = mount(Actions, {
|
|
278
|
+
props: { appointment: confirmableAppointment },
|
|
279
|
+
global: createGlobalConfig()
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(wrapper.find('.full-width-button').exists()).toBe(true);
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe('Component Template Structure', () => {
|
|
287
|
+
it('should render different templates based on status', () => {
|
|
288
|
+
// Test upcoming
|
|
289
|
+
const upcomingAppointment = createMockAppointment({ status: 'upcoming' });
|
|
290
|
+
wrapper = mount(Actions, {
|
|
291
|
+
props: { appointment: upcomingAppointment },
|
|
292
|
+
global: createGlobalConfig()
|
|
293
|
+
});
|
|
294
|
+
expect(wrapper.html()).toContain('Termin stornieren');
|
|
295
|
+
|
|
296
|
+
// Test cancelled
|
|
297
|
+
const cancelledAppointment = createMockAppointment({ status: 'cancelled' });
|
|
298
|
+
wrapper = mount(Actions, {
|
|
299
|
+
props: { appointment: cancelledAppointment },
|
|
300
|
+
global: createGlobalConfig()
|
|
301
|
+
});
|
|
302
|
+
expect(wrapper.html()).toContain('Termin storniert');
|
|
303
|
+
|
|
304
|
+
// Test missed
|
|
305
|
+
const missedAppointment = createMockAppointment({ status: 'missed' } as any);
|
|
306
|
+
wrapper = mount(Actions, {
|
|
307
|
+
props: { appointment: missedAppointment },
|
|
308
|
+
global: createGlobalConfig()
|
|
309
|
+
});
|
|
310
|
+
expect(wrapper.html()).toContain('Termin verpasst');
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe('Props Validation', () => {
|
|
315
|
+
it('should accept appointment prop', () => {
|
|
316
|
+
const appointment = createMockAppointment();
|
|
317
|
+
wrapper = mount(Actions, {
|
|
318
|
+
props: { appointment },
|
|
319
|
+
global: createGlobalConfig()
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
expect(wrapper.props('appointment')).toEqual(appointment);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should handle appointment prop changes', async () => {
|
|
326
|
+
const appointment = createMockAppointment({ status: 'upcoming' });
|
|
327
|
+
wrapper = mount(Actions, {
|
|
328
|
+
props: { appointment },
|
|
329
|
+
global: createGlobalConfig()
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
expect(wrapper.html()).toContain('Termin stornieren');
|
|
333
|
+
|
|
334
|
+
const cancelledAppointment = createMockAppointment({ status: 'cancelled' });
|
|
335
|
+
await wrapper.setProps({ appointment: cancelledAppointment });
|
|
336
|
+
|
|
337
|
+
expect(wrapper.html()).toContain('Termin storniert');
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should accept disabled prop', () => {
|
|
341
|
+
const appointment = createMockAppointment({ status: 'upcoming' });
|
|
342
|
+
wrapper = mount(Actions, {
|
|
343
|
+
props: { appointment, disabled: true },
|
|
344
|
+
global: createGlobalConfig()
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
expect(wrapper.props('disabled')).toBe(true);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
describe('Edge Cases', () => {
|
|
352
|
+
it('should handle appointment with null start gracefully', () => {
|
|
353
|
+
const appointmentWithNulls = createMockAppointment({
|
|
354
|
+
start: null as any,
|
|
355
|
+
is_confirmed: null as any
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
wrapper = mount(Actions, {
|
|
359
|
+
props: { appointment: appointmentWithNulls },
|
|
360
|
+
global: createGlobalConfig()
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
expect(wrapper.exists()).toBe(true);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
describe('Accessibility', () => {
|
|
368
|
+
beforeEach(() => {
|
|
369
|
+
const appointment = createMockAppointment({ status: 'upcoming' });
|
|
370
|
+
wrapper = mount(Actions, {
|
|
371
|
+
props: { appointment },
|
|
372
|
+
global: createGlobalConfig()
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('should have accessible button labels', () => {
|
|
377
|
+
const buttons = wrapper.findAll('.button-stub');
|
|
378
|
+
expect(buttons.length).toBeGreaterThan(0);
|
|
379
|
+
|
|
380
|
+
buttons.forEach(button => {
|
|
381
|
+
const textContent = button.text();
|
|
382
|
+
expect(textContent).toBeTruthy();
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('should provide semantic meaning for action buttons', () => {
|
|
387
|
+
const actionGrid = wrapper.find('.actions-grid');
|
|
388
|
+
expect(actionGrid.exists()).toBe(true);
|
|
389
|
+
|
|
390
|
+
// Check that buttons have meaningful text content
|
|
391
|
+
expect(wrapper.html()).toContain('Termin stornieren');
|
|
392
|
+
expect(wrapper.html()).toContain('Termin verschieben');
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('should have proper semantic structure for confirm button', async () => {
|
|
396
|
+
const futureDate = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString();
|
|
397
|
+
const confirmableAppointment = createMockAppointment({
|
|
398
|
+
start: futureDate,
|
|
399
|
+
is_confirmed: false,
|
|
400
|
+
status: 'upcoming'
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
wrapper = mount(Actions, {
|
|
404
|
+
props: { appointment: confirmableAppointment },
|
|
405
|
+
global: createGlobalConfig()
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
const confirmButton = wrapper.find('.full-width-button');
|
|
409
|
+
expect(confirmButton.exists()).toBe(true);
|
|
410
|
+
expect(wrapper.html()).toContain('Termin bestätigen');
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('should use appropriate structure for different appointment states', async () => {
|
|
414
|
+
// Test upcoming appointment has action grid
|
|
415
|
+
expect(wrapper.find('.actions-grid').exists()).toBe(true);
|
|
416
|
+
|
|
417
|
+
// Test cancelled appointment
|
|
418
|
+
const cancelledAppointment = createMockAppointment({ status: 'cancelled' });
|
|
419
|
+
await wrapper.setProps({ appointment: cancelledAppointment });
|
|
420
|
+
expect(wrapper.html()).toContain('Termin storniert');
|
|
421
|
+
|
|
422
|
+
// Test missed appointment has info box
|
|
423
|
+
const missedAppointment = createMockAppointment({ status: 'missed' } as any);
|
|
424
|
+
await wrapper.setProps({ appointment: missedAppointment });
|
|
425
|
+
expect(wrapper.find('.missed-info-box').exists()).toBe(true);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('should have clear visual hierarchy with CSS classes', () => {
|
|
429
|
+
expect(wrapper.find('.actions-grid').exists()).toBe(true);
|
|
430
|
+
|
|
431
|
+
// Actions should be in a logical visual group
|
|
432
|
+
const actionButtons = wrapper.findAll('.action-button');
|
|
433
|
+
expect(actionButtons.length).toBeGreaterThan(0);
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
});
|