@zap-wunschlachen/wl-shared-components 1.0.25 → 1.0.26
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 +205 -205
- package/.github/workflows/static.yml +61 -61
- package/.github/workflows/update-snapshots.yml +37 -37
- package/.prettierrc +5 -5
- 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 +34 -176
- package/README.md +56 -56
- package/heroicons.ts +75 -75
- package/index.html +19 -19
- package/package.json +67 -67
- 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/src/assets/css/base.css +235 -235
- package/src/assets/css/variables.css +107 -96
- package/src/components/Accordion/Accordion.css +59 -59
- package/src/components/Accordion/AccordionGroup.vue +51 -51
- package/src/components/Accordion/AccordionItem.vue +66 -66
- 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 +15 -15
- package/src/components/Appointment/Card/AnamneseNotification.vue +23 -23
- package/src/components/Appointment/Card/Card.css +80 -80
- package/src/components/Appointment/Card/Card.vue +93 -93
- package/src/components/Appointment/Card/Details.css +50 -50
- package/src/components/Appointment/Card/Details.vue +43 -43
- package/src/components/Audio/Audio.vue +187 -187
- package/src/components/Audio/Waveform.vue +117 -117
- package/src/components/Background/Background.css +39 -0
- package/src/components/Background/Background.vue +19 -0
- package/src/components/Background/WhiteCocoonBackground.vue +9 -0
- package/src/components/Background/WunschlachenBackground.vue +11 -0
- package/src/components/Button/Button.vue +119 -119
- package/src/components/CheckBox/CheckBox.css +185 -185
- package/src/components/CheckBox/Checkbox.vue +130 -130
- package/src/components/DateInput/DateInput.css +2 -2
- package/src/components/DateInput/DateInput.vue +262 -262
- package/src/components/Dialog/Dialog.css +6 -6
- package/src/components/Dialog/Dialog.vue +29 -29
- package/src/components/EditField/EditField.css +19 -19
- package/src/components/EditField/EditField.vue +202 -202
- package/src/components/ErrorPage/ErrorPage.css +124 -0
- package/src/components/ErrorPage/ErrorPage.vue +45 -0
- package/src/components/ErrorPage/ErrorPageLogo.vue +26 -0
- package/src/components/IconBullet/IconBullet.vue +86 -86
- package/src/components/IconBullet/IconBulletList.vue +41 -41
- package/src/components/Icons/AdvanceAppointments.vue +153 -153
- package/src/components/Icons/Audio/CloudFailed.vue +20 -20
- package/src/components/Icons/Audio/CloudSaved.vue +21 -21
- package/src/components/Icons/Audio/Delete.vue +15 -15
- package/src/components/Icons/Audio/Pause.vue +18 -18
- package/src/components/Icons/Audio/Play.vue +15 -15
- package/src/components/Icons/CalendarNotification.vue +126 -126
- package/src/components/Icons/Chair.vue +32 -32
- package/src/components/Icons/ChairNotification.vue +35 -35
- package/src/components/Icons/Circle.vue +66 -66
- package/src/components/Icons/FavIcon.vue +22 -22
- package/src/components/Icons/FilledCircle.vue +11 -11
- package/src/components/Icons/Group3.vue +46 -46
- package/src/components/Icons/Logo.vue +108 -0
- package/src/components/Icons/RingNotification.vue +54 -54
- package/src/components/Icons/SolidArrowRight.vue +14 -14
- package/src/components/Icons/calendar.vue +17 -17
- package/src/components/Icons/checkbox.vue +19 -19
- package/src/components/Icons/outlineChecked.vue +27 -27
- package/src/components/Icons/play.vue +5 -5
- package/src/components/Input/Input.css +187 -187
- package/src/components/Input/Input.vue +247 -247
- 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 +51 -51
- package/src/components/MaintenanceBanner/MaintenanceBanner.css +289 -0
- package/src/components/MaintenanceBanner/MaintenanceBanner.vue +127 -0
- package/src/components/MaintenanceBanner/MaintenanceIllustration.vue +54 -0
- package/src/components/Modal/Modal.css +5 -5
- package/src/components/Modal/Modal.vue +22 -22
- package/src/components/NotificationBubble/NotificationBubble.css +4 -4
- package/src/components/NotificationBubble/NotificationBubble.vue +90 -90
- package/src/components/OtpInput/OtpInput.css +39 -39
- package/src/components/OtpInput/OtpInput.vue +143 -143
- package/src/components/PhoneInput/PhoneInput.css +31 -31
- package/src/components/PhoneInput/PhoneInput.vue +113 -113
- package/src/components/Select/Select.css +150 -150
- package/src/components/Select/Select.vue +315 -315
- package/src/components/TextArea/TextArea.css +3 -3
- package/src/components/TextArea/TextArea.vue +126 -126
- package/src/components/TickBox/TickBox.css +49 -49
- package/src/components/TickBox/TickBox.vue +126 -126
- package/src/components/index.ts +26 -24
- 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 +34 -34
- package/src/main.ts +11 -11
- package/src/plugins/vuetify.ts +139 -131
- package/src/shims-vue.d.ts +10 -10
- package/src/stories/Accordion.stories.ts +650 -650
- package/src/stories/Audio.stories.ts +28 -28
- package/src/stories/Button.stories.ts +263 -263
- package/src/stories/CheckBox.stories.ts +348 -348
- package/src/stories/DateInput.stories.ts +53 -53
- package/src/stories/Dialog.stories.ts +147 -147
- package/src/stories/EditField.stories.ts +78 -78
- package/src/stories/IconBullet/IconBullet.stories.ts +201 -201
- package/src/stories/IconBullet/IconBulletList.stories.ts +275 -275
- package/src/stories/Input.stories.ts +351 -351
- package/src/stories/Laboratory/Cards/AppointmentCard/AppointmentCard.stories.ts +260 -260
- package/src/stories/Laboratory/Cards/DocumentCard/DocumentCard.stories.ts +176 -176
- package/src/stories/Laboratory/Cards/DocumentCard/DocumentCardItem.stories.ts +119 -119
- package/src/stories/Laboratory/Cards/InfoCard/InfoCard.stories.ts +320 -320
- package/src/stories/Laboratory/Cards/TicketCard/TicketCard.stories.ts +335 -335
- package/src/stories/Laboratory/Chat/ChatBoxImage.stories.ts +82 -82
- package/src/stories/Laboratory/Chat/ChatMessage.stories.ts +198 -198
- package/src/stories/Laboratory/Chat/ChatMessageBadge.stories.ts +204 -204
- package/src/stories/Laboratory/Chat/ChatNotification.stories.ts +144 -144
- package/src/stories/Laboratory/Chat/ProgressLinear.stories.ts +186 -186
- package/src/stories/Laboratory/Chat/StatusNotification.stories.ts +111 -111
- package/src/stories/Laboratory/MainColumnsBar.stories.ts +48 -48
- package/src/stories/Laboratory/ProgressCircle.stories.ts +261 -261
- package/src/stories/Laboratory/SelectionColumnBar.stories.ts +234 -234
- package/src/stories/Laboratory/TagLabel.stories.ts +418 -418
- package/src/stories/Laboratory/TagLabelGroup.stories.ts +234 -234
- package/src/stories/Laboratory/Timeline.stories.ts +403 -403
- package/src/stories/NotificationBubble.stories.ts +194 -194
- package/src/stories/OtpInput.stories.ts +100 -100
- package/src/stories/PhoneInput.stories.ts +52 -52
- package/src/stories/Select.stories.ts +419 -419
- package/src/stories/TextArea.stories.ts +112 -112
- package/src/stories/TickBox.stories.ts +294 -294
- package/src/stories/v-icon.stories.ts +91 -91
- package/src/utils/index.ts +106 -100
- package/src/vite-env.d.ts +1 -1
- package/tests/e2e/README.md +220 -220
- package/tests/e2e/accessibility.spec.ts +638 -638
- package/tests/e2e/accordion.spec.ts +42 -42
- package/tests/e2e/additional-components.spec.ts +437 -437
- package/tests/e2e/all-components.spec.ts +135 -135
- package/tests/e2e/appointment-card.spec.ts +816 -816
- package/tests/e2e/button-fixed.spec.ts +58 -58
- package/tests/e2e/button.spec.ts +76 -76
- package/tests/e2e/checkbox.spec.ts +50 -50
- package/tests/e2e/date-input.spec.ts +46 -46
- package/tests/e2e/debug.spec.ts +51 -51
- package/tests/e2e/dialog.spec.ts +58 -58
- package/tests/e2e/input.spec.ts +55 -55
- package/tests/e2e/laboratory-components.spec.ts +320 -320
- package/tests/e2e/otp-input.spec.ts +50 -50
- package/tests/e2e/select.spec.ts +52 -52
- package/tests/e2e/storybook-utils.ts +59 -59
- package/tests/e2e/test-basic.spec.ts +33 -33
- package/tests/e2e/visual-regression.spec.ts +350 -350
- package/tests/unit/components/Accordion/AccordionGroup.spec.ts.skip +342 -342
- package/tests/unit/components/Accordion/AccordionItem.spec.ts.skip +383 -383
- package/tests/unit/components/Appointment/Card/Actions.spec.ts +407 -407
- package/tests/unit/components/Appointment/Card/Card.spec.ts +485 -485
- package/tests/unit/components/Appointment/Card/Details.spec.ts +397 -397
- package/tests/unit/components/Audio/Audio.spec.ts +403 -403
- package/tests/unit/components/Audio/Waveform.spec.ts +483 -483
- package/tests/unit/components/Core/Button.spec.ts +336 -336
- package/tests/unit/components/Core/Checkbox.spec.ts +544 -544
- package/tests/unit/components/Core/DateInput.spec.ts +690 -690
- package/tests/unit/components/Core/Dialog.spec.ts +485 -485
- package/tests/unit/components/Core/EditField.spec.ts +782 -782
- package/tests/unit/components/Core/Input.spec.ts +512 -512
- 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 +619 -619
- package/tests/unit/components/Core/Select.spec.ts +712 -712
- package/tests/unit/components/Core/TextArea.spec.ts +565 -565
- package/tests/unit/components/Core/TickBox.spec.ts +779 -779
- 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/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 +186 -186
- package/tests/unit/components/Icons/Chair.spec.ts +234 -234
- package/tests/unit/components/Icons/ChairNotification.spec.ts +311 -311
- package/tests/unit/components/Icons/Circle.spec.ts +255 -255
- package/tests/unit/components/Icons/FavIcon.spec.ts +251 -251
- package/tests/unit/components/Icons/FilledCircle.spec.ts +274 -274
- package/tests/unit/components/Icons/Group3.spec.ts +355 -355
- package/tests/unit/components/Icons/RingNotification.spec.ts +393 -393
- package/tests/unit/components/Icons/calendar.spec.ts +286 -286
- package/tests/unit/components/Icons/checkbox.spec.ts +315 -315
- package/tests/unit/components/Icons/outlineChecked.spec.ts +434 -434
- package/tests/unit/components/Icons/play.spec.ts +308 -308
- 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/constants/iconEnums.spec.ts +39 -39
- package/tests/unit/i18n/i18n.spec.ts +88 -88
- package/tests/unit/plugins/vuetify.spec.ts +220 -220
- package/tests/unit/setup.ts +189 -189
- 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 +151 -151
- package/tsconfig.json +26 -26
- package/vite.config.ts +29 -29
- package/vitest.config.ts +83 -83
|
@@ -1,408 +1,408 @@
|
|
|
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
|
-
describe('Appointment Card Actions', () => {
|
|
7
|
-
let wrapper: VueWrapper;
|
|
8
|
-
|
|
9
|
-
const createMockAppointment = (overrides: Partial<AppointmentData> = {}): AppointmentData => ({
|
|
10
|
-
id: '123',
|
|
11
|
-
template_name: 'Test Template',
|
|
12
|
-
description: 'Test Description',
|
|
13
|
-
dentist: {
|
|
14
|
-
name: 'Dr. Test',
|
|
15
|
-
gender: 'Male',
|
|
16
|
-
imageSrc: 'test.jpg'
|
|
17
|
-
},
|
|
18
|
-
start: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), // Tomorrow
|
|
19
|
-
type: 1,
|
|
20
|
-
status: 'upcoming',
|
|
21
|
-
is_confirmed: false,
|
|
22
|
-
...overrides
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
beforeEach(() => {
|
|
26
|
-
vi.clearAllMocks();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe('Upcoming Appointments', () => {
|
|
30
|
-
beforeEach(() => {
|
|
31
|
-
const appointment = createMockAppointment({ status: 'upcoming' });
|
|
32
|
-
wrapper = mount(Actions, {
|
|
33
|
-
props: { appointment },
|
|
34
|
-
global: {
|
|
35
|
-
stubs: {
|
|
36
|
-
Button: true
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should render cancel and reschedule buttons for upcoming appointments', () => {
|
|
43
|
-
expect(wrapper.html()).toContain('Termin stornieren');
|
|
44
|
-
expect(wrapper.html()).toContain('Termin verschieben');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should have actions grid structure for upcoming appointments', () => {
|
|
48
|
-
expect(wrapper.find('.actions-grid').exists()).toBe(true);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
describe('Past Appointments', () => {
|
|
53
|
-
beforeEach(() => {
|
|
54
|
-
const appointment = createMockAppointment({ status: 'past' });
|
|
55
|
-
wrapper = mount(Actions, {
|
|
56
|
-
props: { appointment },
|
|
57
|
-
global: {
|
|
58
|
-
stubs: {
|
|
59
|
-
Button: true
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('should render rebook button for past appointments', () => {
|
|
66
|
-
expect(wrapper.html()).toContain('Diesen Termin nochmal buchen');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should not have actions grid for past appointments', () => {
|
|
70
|
-
expect(wrapper.find('.actions-grid').exists()).toBe(false);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
describe('Cancelled Appointments', () => {
|
|
75
|
-
beforeEach(() => {
|
|
76
|
-
const appointment = createMockAppointment({ status: 'cancelled' });
|
|
77
|
-
wrapper = mount(Actions, {
|
|
78
|
-
props: { appointment },
|
|
79
|
-
global: {
|
|
80
|
-
stubs: {
|
|
81
|
-
Button: true
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('should render cancelled status button for cancelled appointments', () => {
|
|
88
|
-
expect(wrapper.html()).toContain('Termin storniert');
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('should not have actions grid for cancelled appointments', () => {
|
|
92
|
-
expect(wrapper.find('.actions-grid').exists()).toBe(false);
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
describe('Confirmable Logic', () => {
|
|
97
|
-
it('should show confirm button when appointment is confirmable', async () => {
|
|
98
|
-
// Create appointment within 48h and unconfirmed
|
|
99
|
-
const futureDate = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString(); // 12 hours from now
|
|
100
|
-
const confirmableAppointment = createMockAppointment({
|
|
101
|
-
start: futureDate,
|
|
102
|
-
is_confirmed: false,
|
|
103
|
-
status: 'upcoming'
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
wrapper = mount(Actions, {
|
|
107
|
-
props: { appointment: confirmableAppointment },
|
|
108
|
-
global: {
|
|
109
|
-
stubs: {
|
|
110
|
-
Button: true
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
expect(wrapper.html()).toContain('Termin bestätigen');
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('should not show confirm button when appointment is already confirmed', async () => {
|
|
119
|
-
const confirmedAppointment = createMockAppointment({
|
|
120
|
-
is_confirmed: true,
|
|
121
|
-
status: 'upcoming'
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
wrapper = mount(Actions, {
|
|
125
|
-
props: { appointment: confirmedAppointment },
|
|
126
|
-
global: {
|
|
127
|
-
stubs: {
|
|
128
|
-
Button: true
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
expect(wrapper.html()).not.toContain('Termin bestätigen');
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('should not show confirm button when appointment is more than 48h away', async () => {
|
|
137
|
-
const distantFuture = new Date(Date.now() + 72 * 60 * 60 * 1000).toISOString(); // 72 hours from now
|
|
138
|
-
const distantAppointment = createMockAppointment({
|
|
139
|
-
start: distantFuture,
|
|
140
|
-
is_confirmed: false,
|
|
141
|
-
status: 'upcoming'
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
wrapper = mount(Actions, {
|
|
145
|
-
props: { appointment: distantAppointment },
|
|
146
|
-
global: {
|
|
147
|
-
stubs: {
|
|
148
|
-
Button: true
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
expect(wrapper.html()).not.toContain('Termin bestätigen');
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('should not show confirm button for past appointments', async () => {
|
|
157
|
-
const pastDate = new Date(Date.now() - 12 * 60 * 60 * 1000).toISOString();
|
|
158
|
-
const pastAppointment = createMockAppointment({
|
|
159
|
-
start: pastDate,
|
|
160
|
-
is_confirmed: false,
|
|
161
|
-
status: 'past'
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
wrapper = mount(Actions, {
|
|
165
|
-
props: { appointment: pastAppointment },
|
|
166
|
-
global: {
|
|
167
|
-
stubs: {
|
|
168
|
-
Button: true
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
expect(wrapper.html()).not.toContain('Termin bestätigen');
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
describe('CSS Classes and Styling', () => {
|
|
178
|
-
beforeEach(() => {
|
|
179
|
-
const appointment = createMockAppointment({ status: 'upcoming' });
|
|
180
|
-
wrapper = mount(Actions, {
|
|
181
|
-
props: { appointment },
|
|
182
|
-
global: {
|
|
183
|
-
stubs: {
|
|
184
|
-
Button: true
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('should apply correct CSS classes', () => {
|
|
191
|
-
expect(wrapper.find('.actions-grid').exists()).toBe(true);
|
|
192
|
-
expect(wrapper.findAll('.action-button')).toHaveLength(2);
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it('should have full-width button class when confirmable', async () => {
|
|
196
|
-
const futureDate = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString();
|
|
197
|
-
const confirmableAppointment = createMockAppointment({
|
|
198
|
-
start: futureDate,
|
|
199
|
-
is_confirmed: false,
|
|
200
|
-
status: 'upcoming'
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
await wrapper.setProps({ appointment: confirmableAppointment });
|
|
204
|
-
expect(wrapper.find('.full-width-button').exists()).toBe(true);
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
describe('Component Template Structure', () => {
|
|
209
|
-
it('should render different templates based on status', () => {
|
|
210
|
-
// Test upcoming
|
|
211
|
-
const upcomingAppointment = createMockAppointment({ status: 'upcoming' });
|
|
212
|
-
wrapper = mount(Actions, {
|
|
213
|
-
props: { appointment: upcomingAppointment },
|
|
214
|
-
global: { stubs: { Button: true } }
|
|
215
|
-
});
|
|
216
|
-
expect(wrapper.html()).toContain('Termin stornieren');
|
|
217
|
-
|
|
218
|
-
// Test past
|
|
219
|
-
const pastAppointment = createMockAppointment({ status: 'past' });
|
|
220
|
-
wrapper = mount(Actions, {
|
|
221
|
-
props: { appointment: pastAppointment },
|
|
222
|
-
global: { stubs: { Button: true } }
|
|
223
|
-
});
|
|
224
|
-
expect(wrapper.html()).toContain('Diesen Termin nochmal buchen');
|
|
225
|
-
|
|
226
|
-
// Test cancelled
|
|
227
|
-
const cancelledAppointment = createMockAppointment({ status: 'cancelled' });
|
|
228
|
-
wrapper = mount(Actions, {
|
|
229
|
-
props: { appointment: cancelledAppointment },
|
|
230
|
-
global: { stubs: { Button: true } }
|
|
231
|
-
});
|
|
232
|
-
expect(wrapper.html()).toContain('Termin storniert');
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
describe('Props Validation', () => {
|
|
237
|
-
it('should accept appointment prop', () => {
|
|
238
|
-
const appointment = createMockAppointment();
|
|
239
|
-
wrapper = mount(Actions, {
|
|
240
|
-
props: { appointment },
|
|
241
|
-
global: { stubs: { Button: true } }
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
expect(wrapper.props('appointment')).toEqual(appointment);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it('should handle appointment prop changes', async () => {
|
|
248
|
-
const appointment = createMockAppointment({ status: 'upcoming' });
|
|
249
|
-
wrapper = mount(Actions, {
|
|
250
|
-
props: { appointment },
|
|
251
|
-
global: { stubs: { Button: true } }
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
expect(wrapper.html()).toContain('Termin stornieren');
|
|
255
|
-
|
|
256
|
-
const pastAppointment = createMockAppointment({ status: 'past' });
|
|
257
|
-
await wrapper.setProps({ appointment: pastAppointment });
|
|
258
|
-
|
|
259
|
-
expect(wrapper.html()).toContain('Diesen Termin nochmal buchen');
|
|
260
|
-
});
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
describe('Edge Cases', () => {
|
|
264
|
-
it('should handle appointment with null values gracefully', () => {
|
|
265
|
-
const appointmentWithNulls = createMockAppointment({
|
|
266
|
-
start: null as any,
|
|
267
|
-
is_confirmed: null as any
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
wrapper = mount(Actions, {
|
|
271
|
-
props: { appointment: appointmentWithNulls },
|
|
272
|
-
global: { stubs: { Button: true } }
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
expect(wrapper.exists()).toBe(true);
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
describe('Accessibility', () => {
|
|
280
|
-
beforeEach(() => {
|
|
281
|
-
const appointment = createMockAppointment({ status: 'upcoming' });
|
|
282
|
-
wrapper = mount(Actions, {
|
|
283
|
-
props: { appointment },
|
|
284
|
-
global: {
|
|
285
|
-
stubs: {
|
|
286
|
-
Button: {
|
|
287
|
-
template: '<button class="button-stub" :aria-label="label" :disabled="disabled" :tabindex="disabled ? -1 : 0" role="button"><slot>{{ label }}</slot></button>',
|
|
288
|
-
props: ['variant', 'prependIcon', 'size', 'label', 'readonly', 'disabled', 'color']
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
it('should have accessible button labels', () => {
|
|
296
|
-
const buttons = wrapper.findAll('.button-stub');
|
|
297
|
-
expect(buttons.length).toBeGreaterThan(0);
|
|
298
|
-
|
|
299
|
-
buttons.forEach(button => {
|
|
300
|
-
const ariaLabel = button.attributes('aria-label');
|
|
301
|
-
const textContent = button.text();
|
|
302
|
-
expect(ariaLabel || textContent).toBeTruthy();
|
|
303
|
-
});
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it('should have proper button roles', () => {
|
|
307
|
-
const buttons = wrapper.findAll('.button-stub');
|
|
308
|
-
|
|
309
|
-
buttons.forEach(button => {
|
|
310
|
-
expect(button.attributes('role')).toBe('button');
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
it('should have keyboard accessibility attributes', () => {
|
|
315
|
-
const buttons = wrapper.findAll('.button-stub');
|
|
316
|
-
|
|
317
|
-
buttons.forEach(button => {
|
|
318
|
-
const tabIndex = button.attributes('tabindex');
|
|
319
|
-
expect(tabIndex).toBe('0');
|
|
320
|
-
});
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
it('should indicate disabled state for readonly buttons', async () => {
|
|
324
|
-
const cancelledAppointment = createMockAppointment({ status: 'cancelled' });
|
|
325
|
-
|
|
326
|
-
wrapper = mount(Actions, {
|
|
327
|
-
props: { appointment: cancelledAppointment },
|
|
328
|
-
global: {
|
|
329
|
-
stubs: {
|
|
330
|
-
Button: true
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
// Check that the cancelled button content is rendered
|
|
336
|
-
expect(wrapper.html()).toContain('Termin storniert');
|
|
337
|
-
|
|
338
|
-
// The button should not have actions grid (cancelled appointments show single readonly button)
|
|
339
|
-
expect(wrapper.find('.actions-grid').exists()).toBe(false);
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
it('should provide semantic meaning for action buttons', () => {
|
|
343
|
-
const actionGrid = wrapper.find('.actions-grid');
|
|
344
|
-
expect(actionGrid.exists()).toBe(true);
|
|
345
|
-
|
|
346
|
-
const buttons = wrapper.findAll('.action-button');
|
|
347
|
-
expect(buttons.length).toBe(2);
|
|
348
|
-
|
|
349
|
-
// Check that buttons have meaningful text content
|
|
350
|
-
expect(wrapper.html()).toContain('Termin stornieren');
|
|
351
|
-
expect(wrapper.html()).toContain('Termin verschieben');
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
it('should have proper semantic structure for confirm button', async () => {
|
|
355
|
-
const futureDate = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString();
|
|
356
|
-
const confirmableAppointment = createMockAppointment({
|
|
357
|
-
start: futureDate,
|
|
358
|
-
is_confirmed: false,
|
|
359
|
-
status: 'upcoming'
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
await wrapper.setProps({ appointment: confirmableAppointment });
|
|
363
|
-
|
|
364
|
-
const confirmButton = wrapper.find('.full-width-button');
|
|
365
|
-
expect(confirmButton.exists()).toBe(true);
|
|
366
|
-
expect(wrapper.html()).toContain('Termin bestätigen');
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
it('should maintain focus management for button interactions', () => {
|
|
370
|
-
const buttons = wrapper.findAll('.button-stub');
|
|
371
|
-
|
|
372
|
-
// All interactive buttons should be focusable
|
|
373
|
-
buttons.forEach(button => {
|
|
374
|
-
const tabIndex = button.attributes('tabindex');
|
|
375
|
-
expect(parseInt(tabIndex || '0')).toBeGreaterThanOrEqual(0);
|
|
376
|
-
});
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
it('should use appropriate ARIA attributes for different appointment states', async () => {
|
|
380
|
-
// Test upcoming appointment
|
|
381
|
-
let buttons = wrapper.findAll('.button-stub');
|
|
382
|
-
expect(buttons.length).toBeGreaterThan(0);
|
|
383
|
-
|
|
384
|
-
// Test past appointment
|
|
385
|
-
const pastAppointment = createMockAppointment({ status: 'past' });
|
|
386
|
-
await wrapper.setProps({ appointment: pastAppointment });
|
|
387
|
-
|
|
388
|
-
expect(wrapper.html()).toContain('Diesen Termin nochmal buchen');
|
|
389
|
-
|
|
390
|
-
// Test cancelled appointment
|
|
391
|
-
const cancelledAppointment = createMockAppointment({ status: 'cancelled' });
|
|
392
|
-
await wrapper.setProps({ appointment: cancelledAppointment });
|
|
393
|
-
|
|
394
|
-
expect(wrapper.html()).toContain('Termin storniert');
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
it('should have clear visual hierarchy with CSS classes', () => {
|
|
398
|
-
expect(wrapper.find('.actions-grid').exists()).toBe(true);
|
|
399
|
-
expect(wrapper.findAll('.action-button')).toHaveLength(2);
|
|
400
|
-
|
|
401
|
-
// Actions should be in a logical visual group
|
|
402
|
-
const actionButtons = wrapper.findAll('.action-button');
|
|
403
|
-
actionButtons.forEach(button => {
|
|
404
|
-
expect(button.classes()).toContain('action-button');
|
|
405
|
-
});
|
|
406
|
-
});
|
|
407
|
-
});
|
|
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
|
+
describe('Appointment Card Actions', () => {
|
|
7
|
+
let wrapper: VueWrapper;
|
|
8
|
+
|
|
9
|
+
const createMockAppointment = (overrides: Partial<AppointmentData> = {}): AppointmentData => ({
|
|
10
|
+
id: '123',
|
|
11
|
+
template_name: 'Test Template',
|
|
12
|
+
description: 'Test Description',
|
|
13
|
+
dentist: {
|
|
14
|
+
name: 'Dr. Test',
|
|
15
|
+
gender: 'Male',
|
|
16
|
+
imageSrc: 'test.jpg'
|
|
17
|
+
},
|
|
18
|
+
start: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), // Tomorrow
|
|
19
|
+
type: 1,
|
|
20
|
+
status: 'upcoming',
|
|
21
|
+
is_confirmed: false,
|
|
22
|
+
...overrides
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
vi.clearAllMocks();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('Upcoming Appointments', () => {
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
const appointment = createMockAppointment({ status: 'upcoming' });
|
|
32
|
+
wrapper = mount(Actions, {
|
|
33
|
+
props: { appointment },
|
|
34
|
+
global: {
|
|
35
|
+
stubs: {
|
|
36
|
+
Button: true
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should render cancel and reschedule buttons for upcoming appointments', () => {
|
|
43
|
+
expect(wrapper.html()).toContain('Termin stornieren');
|
|
44
|
+
expect(wrapper.html()).toContain('Termin verschieben');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should have actions grid structure for upcoming appointments', () => {
|
|
48
|
+
expect(wrapper.find('.actions-grid').exists()).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('Past Appointments', () => {
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
const appointment = createMockAppointment({ status: 'past' });
|
|
55
|
+
wrapper = mount(Actions, {
|
|
56
|
+
props: { appointment },
|
|
57
|
+
global: {
|
|
58
|
+
stubs: {
|
|
59
|
+
Button: true
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should render rebook button for past appointments', () => {
|
|
66
|
+
expect(wrapper.html()).toContain('Diesen Termin nochmal buchen');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should not have actions grid for past appointments', () => {
|
|
70
|
+
expect(wrapper.find('.actions-grid').exists()).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('Cancelled Appointments', () => {
|
|
75
|
+
beforeEach(() => {
|
|
76
|
+
const appointment = createMockAppointment({ status: 'cancelled' });
|
|
77
|
+
wrapper = mount(Actions, {
|
|
78
|
+
props: { appointment },
|
|
79
|
+
global: {
|
|
80
|
+
stubs: {
|
|
81
|
+
Button: true
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should render cancelled status button for cancelled appointments', () => {
|
|
88
|
+
expect(wrapper.html()).toContain('Termin storniert');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should not have actions grid for cancelled appointments', () => {
|
|
92
|
+
expect(wrapper.find('.actions-grid').exists()).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('Confirmable Logic', () => {
|
|
97
|
+
it('should show confirm button when appointment is confirmable', async () => {
|
|
98
|
+
// Create appointment within 48h and unconfirmed
|
|
99
|
+
const futureDate = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString(); // 12 hours from now
|
|
100
|
+
const confirmableAppointment = createMockAppointment({
|
|
101
|
+
start: futureDate,
|
|
102
|
+
is_confirmed: false,
|
|
103
|
+
status: 'upcoming'
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
wrapper = mount(Actions, {
|
|
107
|
+
props: { appointment: confirmableAppointment },
|
|
108
|
+
global: {
|
|
109
|
+
stubs: {
|
|
110
|
+
Button: true
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(wrapper.html()).toContain('Termin bestätigen');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should not show confirm button when appointment is already confirmed', async () => {
|
|
119
|
+
const confirmedAppointment = createMockAppointment({
|
|
120
|
+
is_confirmed: true,
|
|
121
|
+
status: 'upcoming'
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
wrapper = mount(Actions, {
|
|
125
|
+
props: { appointment: confirmedAppointment },
|
|
126
|
+
global: {
|
|
127
|
+
stubs: {
|
|
128
|
+
Button: true
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(wrapper.html()).not.toContain('Termin bestätigen');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should not show confirm button when appointment is more than 48h away', async () => {
|
|
137
|
+
const distantFuture = new Date(Date.now() + 72 * 60 * 60 * 1000).toISOString(); // 72 hours from now
|
|
138
|
+
const distantAppointment = createMockAppointment({
|
|
139
|
+
start: distantFuture,
|
|
140
|
+
is_confirmed: false,
|
|
141
|
+
status: 'upcoming'
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
wrapper = mount(Actions, {
|
|
145
|
+
props: { appointment: distantAppointment },
|
|
146
|
+
global: {
|
|
147
|
+
stubs: {
|
|
148
|
+
Button: true
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(wrapper.html()).not.toContain('Termin bestätigen');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should not show confirm button for past appointments', async () => {
|
|
157
|
+
const pastDate = new Date(Date.now() - 12 * 60 * 60 * 1000).toISOString();
|
|
158
|
+
const pastAppointment = createMockAppointment({
|
|
159
|
+
start: pastDate,
|
|
160
|
+
is_confirmed: false,
|
|
161
|
+
status: 'past'
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
wrapper = mount(Actions, {
|
|
165
|
+
props: { appointment: pastAppointment },
|
|
166
|
+
global: {
|
|
167
|
+
stubs: {
|
|
168
|
+
Button: true
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(wrapper.html()).not.toContain('Termin bestätigen');
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('CSS Classes and Styling', () => {
|
|
178
|
+
beforeEach(() => {
|
|
179
|
+
const appointment = createMockAppointment({ status: 'upcoming' });
|
|
180
|
+
wrapper = mount(Actions, {
|
|
181
|
+
props: { appointment },
|
|
182
|
+
global: {
|
|
183
|
+
stubs: {
|
|
184
|
+
Button: true
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should apply correct CSS classes', () => {
|
|
191
|
+
expect(wrapper.find('.actions-grid').exists()).toBe(true);
|
|
192
|
+
expect(wrapper.findAll('.action-button')).toHaveLength(2);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should have full-width button class when confirmable', async () => {
|
|
196
|
+
const futureDate = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString();
|
|
197
|
+
const confirmableAppointment = createMockAppointment({
|
|
198
|
+
start: futureDate,
|
|
199
|
+
is_confirmed: false,
|
|
200
|
+
status: 'upcoming'
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await wrapper.setProps({ appointment: confirmableAppointment });
|
|
204
|
+
expect(wrapper.find('.full-width-button').exists()).toBe(true);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('Component Template Structure', () => {
|
|
209
|
+
it('should render different templates based on status', () => {
|
|
210
|
+
// Test upcoming
|
|
211
|
+
const upcomingAppointment = createMockAppointment({ status: 'upcoming' });
|
|
212
|
+
wrapper = mount(Actions, {
|
|
213
|
+
props: { appointment: upcomingAppointment },
|
|
214
|
+
global: { stubs: { Button: true } }
|
|
215
|
+
});
|
|
216
|
+
expect(wrapper.html()).toContain('Termin stornieren');
|
|
217
|
+
|
|
218
|
+
// Test past
|
|
219
|
+
const pastAppointment = createMockAppointment({ status: 'past' });
|
|
220
|
+
wrapper = mount(Actions, {
|
|
221
|
+
props: { appointment: pastAppointment },
|
|
222
|
+
global: { stubs: { Button: true } }
|
|
223
|
+
});
|
|
224
|
+
expect(wrapper.html()).toContain('Diesen Termin nochmal buchen');
|
|
225
|
+
|
|
226
|
+
// Test cancelled
|
|
227
|
+
const cancelledAppointment = createMockAppointment({ status: 'cancelled' });
|
|
228
|
+
wrapper = mount(Actions, {
|
|
229
|
+
props: { appointment: cancelledAppointment },
|
|
230
|
+
global: { stubs: { Button: true } }
|
|
231
|
+
});
|
|
232
|
+
expect(wrapper.html()).toContain('Termin storniert');
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe('Props Validation', () => {
|
|
237
|
+
it('should accept appointment prop', () => {
|
|
238
|
+
const appointment = createMockAppointment();
|
|
239
|
+
wrapper = mount(Actions, {
|
|
240
|
+
props: { appointment },
|
|
241
|
+
global: { stubs: { Button: true } }
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
expect(wrapper.props('appointment')).toEqual(appointment);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should handle appointment prop changes', async () => {
|
|
248
|
+
const appointment = createMockAppointment({ status: 'upcoming' });
|
|
249
|
+
wrapper = mount(Actions, {
|
|
250
|
+
props: { appointment },
|
|
251
|
+
global: { stubs: { Button: true } }
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
expect(wrapper.html()).toContain('Termin stornieren');
|
|
255
|
+
|
|
256
|
+
const pastAppointment = createMockAppointment({ status: 'past' });
|
|
257
|
+
await wrapper.setProps({ appointment: pastAppointment });
|
|
258
|
+
|
|
259
|
+
expect(wrapper.html()).toContain('Diesen Termin nochmal buchen');
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
describe('Edge Cases', () => {
|
|
264
|
+
it('should handle appointment with null values gracefully', () => {
|
|
265
|
+
const appointmentWithNulls = createMockAppointment({
|
|
266
|
+
start: null as any,
|
|
267
|
+
is_confirmed: null as any
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
wrapper = mount(Actions, {
|
|
271
|
+
props: { appointment: appointmentWithNulls },
|
|
272
|
+
global: { stubs: { Button: true } }
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
expect(wrapper.exists()).toBe(true);
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
describe('Accessibility', () => {
|
|
280
|
+
beforeEach(() => {
|
|
281
|
+
const appointment = createMockAppointment({ status: 'upcoming' });
|
|
282
|
+
wrapper = mount(Actions, {
|
|
283
|
+
props: { appointment },
|
|
284
|
+
global: {
|
|
285
|
+
stubs: {
|
|
286
|
+
Button: {
|
|
287
|
+
template: '<button class="button-stub" :aria-label="label" :disabled="disabled" :tabindex="disabled ? -1 : 0" role="button"><slot>{{ label }}</slot></button>',
|
|
288
|
+
props: ['variant', 'prependIcon', 'size', 'label', 'readonly', 'disabled', 'color']
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should have accessible button labels', () => {
|
|
296
|
+
const buttons = wrapper.findAll('.button-stub');
|
|
297
|
+
expect(buttons.length).toBeGreaterThan(0);
|
|
298
|
+
|
|
299
|
+
buttons.forEach(button => {
|
|
300
|
+
const ariaLabel = button.attributes('aria-label');
|
|
301
|
+
const textContent = button.text();
|
|
302
|
+
expect(ariaLabel || textContent).toBeTruthy();
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('should have proper button roles', () => {
|
|
307
|
+
const buttons = wrapper.findAll('.button-stub');
|
|
308
|
+
|
|
309
|
+
buttons.forEach(button => {
|
|
310
|
+
expect(button.attributes('role')).toBe('button');
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('should have keyboard accessibility attributes', () => {
|
|
315
|
+
const buttons = wrapper.findAll('.button-stub');
|
|
316
|
+
|
|
317
|
+
buttons.forEach(button => {
|
|
318
|
+
const tabIndex = button.attributes('tabindex');
|
|
319
|
+
expect(tabIndex).toBe('0');
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should indicate disabled state for readonly buttons', async () => {
|
|
324
|
+
const cancelledAppointment = createMockAppointment({ status: 'cancelled' });
|
|
325
|
+
|
|
326
|
+
wrapper = mount(Actions, {
|
|
327
|
+
props: { appointment: cancelledAppointment },
|
|
328
|
+
global: {
|
|
329
|
+
stubs: {
|
|
330
|
+
Button: true
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Check that the cancelled button content is rendered
|
|
336
|
+
expect(wrapper.html()).toContain('Termin storniert');
|
|
337
|
+
|
|
338
|
+
// The button should not have actions grid (cancelled appointments show single readonly button)
|
|
339
|
+
expect(wrapper.find('.actions-grid').exists()).toBe(false);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('should provide semantic meaning for action buttons', () => {
|
|
343
|
+
const actionGrid = wrapper.find('.actions-grid');
|
|
344
|
+
expect(actionGrid.exists()).toBe(true);
|
|
345
|
+
|
|
346
|
+
const buttons = wrapper.findAll('.action-button');
|
|
347
|
+
expect(buttons.length).toBe(2);
|
|
348
|
+
|
|
349
|
+
// Check that buttons have meaningful text content
|
|
350
|
+
expect(wrapper.html()).toContain('Termin stornieren');
|
|
351
|
+
expect(wrapper.html()).toContain('Termin verschieben');
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should have proper semantic structure for confirm button', async () => {
|
|
355
|
+
const futureDate = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString();
|
|
356
|
+
const confirmableAppointment = createMockAppointment({
|
|
357
|
+
start: futureDate,
|
|
358
|
+
is_confirmed: false,
|
|
359
|
+
status: 'upcoming'
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
await wrapper.setProps({ appointment: confirmableAppointment });
|
|
363
|
+
|
|
364
|
+
const confirmButton = wrapper.find('.full-width-button');
|
|
365
|
+
expect(confirmButton.exists()).toBe(true);
|
|
366
|
+
expect(wrapper.html()).toContain('Termin bestätigen');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('should maintain focus management for button interactions', () => {
|
|
370
|
+
const buttons = wrapper.findAll('.button-stub');
|
|
371
|
+
|
|
372
|
+
// All interactive buttons should be focusable
|
|
373
|
+
buttons.forEach(button => {
|
|
374
|
+
const tabIndex = button.attributes('tabindex');
|
|
375
|
+
expect(parseInt(tabIndex || '0')).toBeGreaterThanOrEqual(0);
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('should use appropriate ARIA attributes for different appointment states', async () => {
|
|
380
|
+
// Test upcoming appointment
|
|
381
|
+
let buttons = wrapper.findAll('.button-stub');
|
|
382
|
+
expect(buttons.length).toBeGreaterThan(0);
|
|
383
|
+
|
|
384
|
+
// Test past appointment
|
|
385
|
+
const pastAppointment = createMockAppointment({ status: 'past' });
|
|
386
|
+
await wrapper.setProps({ appointment: pastAppointment });
|
|
387
|
+
|
|
388
|
+
expect(wrapper.html()).toContain('Diesen Termin nochmal buchen');
|
|
389
|
+
|
|
390
|
+
// Test cancelled appointment
|
|
391
|
+
const cancelledAppointment = createMockAppointment({ status: 'cancelled' });
|
|
392
|
+
await wrapper.setProps({ appointment: cancelledAppointment });
|
|
393
|
+
|
|
394
|
+
expect(wrapper.html()).toContain('Termin storniert');
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it('should have clear visual hierarchy with CSS classes', () => {
|
|
398
|
+
expect(wrapper.find('.actions-grid').exists()).toBe(true);
|
|
399
|
+
expect(wrapper.findAll('.action-button')).toHaveLength(2);
|
|
400
|
+
|
|
401
|
+
// Actions should be in a logical visual group
|
|
402
|
+
const actionButtons = wrapper.findAll('.action-button');
|
|
403
|
+
actionButtons.forEach(button => {
|
|
404
|
+
expect(button.classes()).toContain('action-button');
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
408
|
});
|