@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.
Files changed (227) hide show
  1. package/.github/workflows/playwright.yml +229 -229
  2. package/.github/workflows/static.yml +61 -61
  3. package/.github/workflows/update-snapshots.yml +37 -37
  4. package/.prettierrc.json +8 -8
  5. package/.storybook/main.ts +18 -18
  6. package/.storybook/preview.ts +37 -37
  7. package/.storybook/storyWrapper.vue +18 -18
  8. package/.storybook/withVuetifyTheme.decorator.ts +21 -21
  9. package/App.vue +139 -139
  10. package/README.md +56 -56
  11. package/docs/assets.md +62 -62
  12. package/heroicons.ts +75 -75
  13. package/index.html +19 -19
  14. package/package.json +71 -71
  15. package/playwright.config.ts +48 -48
  16. package/public/background.svg +60 -60
  17. package/public/style.css +187 -187
  18. package/public/technologies.svg +22 -22
  19. package/scripts/check-translations.ts +352 -352
  20. package/src/assets/css/base.css +242 -242
  21. package/src/assets/css/variables.css +176 -176
  22. package/src/components/Accordion/Accordion.css +65 -65
  23. package/src/components/Accordion/AccordionGroup.vue +88 -88
  24. package/src/components/Accordion/AccordionItem.vue +272 -272
  25. package/src/components/Accordion/presets/default.css +4 -4
  26. package/src/components/Accordion/presets/elevated.css +25 -25
  27. package/src/components/Accordion/presets/filled.css +26 -26
  28. package/src/components/Accordion/presets/index.css +5 -5
  29. package/src/components/Accordion/presets/plain.css +34 -34
  30. package/src/components/Appointment/Card/Actions.css +54 -54
  31. package/src/components/Appointment/Card/Actions.vue +99 -99
  32. package/src/components/Appointment/Card/AnamneseNotification.css +20 -20
  33. package/src/components/Appointment/Card/AnamneseNotification.vue +23 -23
  34. package/src/components/Appointment/Card/Card.css +99 -99
  35. package/src/components/Appointment/Card/Card.vue +97 -97
  36. package/src/components/Appointment/Card/Details.css +62 -62
  37. package/src/components/Appointment/Card/Details.vue +44 -44
  38. package/src/components/Audio/Audio.vue +187 -187
  39. package/src/components/Audio/Waveform.vue +118 -118
  40. package/src/components/Banner/Banner.css +29 -29
  41. package/src/components/Banner/Banner.vue +89 -89
  42. package/src/components/Button/Button.vue +257 -257
  43. package/src/components/CheckBox/CheckBox.css +234 -234
  44. package/src/components/CheckBox/Checkbox.vue +184 -184
  45. package/src/components/DateInput/DateInput.css +2 -2
  46. package/src/components/DateInput/DateInput.vue +376 -370
  47. package/src/components/Dialog/Dialog.css +6 -6
  48. package/src/components/Dialog/Dialog.vue +46 -46
  49. package/src/components/EditField/EditField.css +19 -19
  50. package/src/components/EditField/EditField.vue +211 -211
  51. package/src/components/ErrorPage/ErrorPage.css +172 -172
  52. package/src/components/IconBullet/IconBullet.vue +104 -104
  53. package/src/components/IconBullet/IconBulletList.vue +55 -55
  54. package/src/components/Icons/AdvanceAppointments.vue +161 -161
  55. package/src/components/Icons/Audio/CloudFailed.vue +27 -27
  56. package/src/components/Icons/Audio/CloudSaved.vue +28 -28
  57. package/src/components/Icons/Audio/Delete.vue +22 -22
  58. package/src/components/Icons/Audio/Pause.vue +25 -25
  59. package/src/components/Icons/Audio/Play.vue +22 -22
  60. package/src/components/Icons/Calendar.vue +28 -28
  61. package/src/components/Icons/CalendarNotification.vue +137 -137
  62. package/src/components/Icons/Chair.vue +43 -43
  63. package/src/components/Icons/ChairNotification.vue +46 -46
  64. package/src/components/Icons/Circle.vue +66 -66
  65. package/src/components/Icons/FavIcon.vue +69 -69
  66. package/src/components/Icons/FilledCircle.vue +11 -11
  67. package/src/components/Icons/Group3.vue +57 -57
  68. package/src/components/Icons/Play.vue +16 -16
  69. package/src/components/Icons/RingNotification.vue +65 -65
  70. package/src/components/Icons/SolidArrowRight.vue +14 -14
  71. package/src/components/Icons/checkbox.vue +19 -19
  72. package/src/components/Icons/outlineChecked.vue +38 -38
  73. package/src/components/Input/Input.css +234 -234
  74. package/src/components/Input/Input.vue +281 -281
  75. package/src/components/Laboratory/AppointmentCard/AppointmentCard.css +7 -7
  76. package/src/components/Laboratory/AppointmentCard/AppointmentCard.vue +116 -116
  77. package/src/components/Laboratory/ChatBoxImage/ChatBoxImage.vue +81 -81
  78. package/src/components/Laboratory/ChatMessage/ChatMessage.vue +113 -113
  79. package/src/components/Laboratory/ChatMessage/ChatMessageBadge.css +4 -4
  80. package/src/components/Laboratory/ChatMessage/ChatMessageBadge.vue +99 -99
  81. package/src/components/Laboratory/ChatNotification/ChatNotification.vue +130 -130
  82. package/src/components/Laboratory/DocumentCard/DocumentCard.css +3 -3
  83. package/src/components/Laboratory/DocumentCard/DocumentCard.vue +50 -50
  84. package/src/components/Laboratory/DocumentCard/DocumentCardItem.vue +53 -53
  85. package/src/components/Laboratory/InfoCard/InfoCard.vue +162 -162
  86. package/src/components/Laboratory/MainColumnsBar/MainColumnsBar.vue +102 -102
  87. package/src/components/Laboratory/ProgressCircle/ProgressCircle.vue +152 -152
  88. package/src/components/Laboratory/ProgressLinear/ProgressLinear.css +33 -33
  89. package/src/components/Laboratory/ProgressLinear/ProgressLinear.vue +75 -75
  90. package/src/components/Laboratory/SelectionColumnBar/SelectionColumnBar.vue +92 -92
  91. package/src/components/Laboratory/StatusNotification/StatusNotification.vue +49 -49
  92. package/src/components/Laboratory/TagLabel/TagLabel.vue +126 -126
  93. package/src/components/Laboratory/TagLabelGroup/TagLabelGroup.vue +97 -97
  94. package/src/components/Laboratory/TicketCard/TicketCard.css +3 -3
  95. package/src/components/Laboratory/TicketCard/TicketCard.vue +143 -143
  96. package/src/components/Laboratory/TimeLine/TimeLineEvent.css +18 -18
  97. package/src/components/Laboratory/TimeLine/TimeLineEvent.vue +119 -119
  98. package/src/components/Laboratory/TimeLine/Timeline.css +4 -4
  99. package/src/components/Laboratory/TimeLine/Timeline.vue +30 -30
  100. package/src/components/Loader/Loader.css +78 -78
  101. package/src/components/MaintenanceBanner/MaintenanceBanner.css +353 -353
  102. package/src/components/MaintenanceBanner/MaintenanceBanner.vue +140 -140
  103. package/src/components/MaintenanceBanner/MaintenanceIllustration.vue +54 -54
  104. package/src/components/Modal/Modal.css +5 -5
  105. package/src/components/Modal/Modal.vue +29 -29
  106. package/src/components/NotificationBubble/NotificationBubble.css +4 -4
  107. package/src/components/NotificationBubble/NotificationBubble.vue +90 -90
  108. package/src/components/OtpInput/OtpInput.css +43 -43
  109. package/src/components/OtpInput/OtpInput.vue +181 -181
  110. package/src/components/PhoneInput/PhoneInput.css +151 -126
  111. package/src/components/PhoneInput/PhoneInput.vue +230 -139
  112. package/src/components/RadioGroup/RadioGroup.css +65 -0
  113. package/src/components/RadioGroup/RadioGroup.vue +134 -0
  114. package/src/components/Select/Select.css +172 -172
  115. package/src/components/Select/Select.vue +377 -377
  116. package/src/components/SelectAutocomplete/SelectAutocomplete.css +172 -172
  117. package/src/components/SelectAutocomplete/SelectAutocomplete.vue +414 -414
  118. package/src/components/TextArea/TextArea.css +269 -269
  119. package/src/components/TextArea/TextArea.vue +207 -207
  120. package/src/components/TickBox/TickBox.css +116 -116
  121. package/src/components/TickBox/TickBox.vue +172 -172
  122. package/src/components/Tile/Tile.css +106 -106
  123. package/src/components/Tile/Tile.vue +173 -173
  124. package/src/components/accessibility.css +218 -218
  125. package/src/components/index.ts +110 -109
  126. package/src/constants/iconEnums.ts +3 -3
  127. package/src/i18n/i18n.ts +15 -15
  128. package/src/i18n/locales/de.json +30 -30
  129. package/src/i18n/locales/en.json +30 -30
  130. package/src/index.ts +43 -43
  131. package/src/main.ts +11 -11
  132. package/src/pages/AccordionGroupPage.vue +873 -873
  133. package/src/pages/AllPage.vue +2483 -2365
  134. package/src/pages/SelectPage.vue +1302 -1302
  135. package/src/pages/TilePage.vue +902 -902
  136. package/src/plugins/vuetify.ts +54 -54
  137. package/src/shims-vue.d.ts +30 -30
  138. package/src/utils/index.ts +733 -733
  139. package/src/vite-env.d.ts +1 -1
  140. package/tests/unit/accessibility/component-a11y.spec.ts +657 -657
  141. package/tests/unit/components/Accordion/AccordionGroup.spec.ts +228 -228
  142. package/tests/unit/components/Accordion/AccordionItem.spec.ts +257 -257
  143. package/tests/unit/components/Appointment/AnamneseNotification.spec.ts +176 -176
  144. package/tests/unit/components/Appointment/Card/Actions.spec.ts +436 -436
  145. package/tests/unit/components/Appointment/Card/Card.spec.ts +531 -531
  146. package/tests/unit/components/Appointment/Card/Details.spec.ts +395 -395
  147. package/tests/unit/components/Audio/Audio.spec.ts +403 -403
  148. package/tests/unit/components/Audio/Waveform.spec.ts +483 -483
  149. package/tests/unit/components/Background/Background.spec.ts +177 -177
  150. package/tests/unit/components/Core/AnamneseAnswerDialog.spec.ts +344 -0
  151. package/tests/unit/components/Core/Banner.spec.ts +187 -0
  152. package/tests/unit/components/Core/Button.spec.ts +346 -346
  153. package/tests/unit/components/Core/Checkbox.spec.ts +544 -544
  154. package/tests/unit/components/Core/DateInput.spec.ts +702 -702
  155. package/tests/unit/components/Core/Dialog.spec.ts +448 -448
  156. package/tests/unit/components/Core/EditField.spec.ts +541 -541
  157. package/tests/unit/components/Core/Input.spec.ts +512 -512
  158. package/tests/unit/components/Core/List.spec.ts +163 -0
  159. package/tests/unit/components/Core/ListItem.spec.ts +205 -0
  160. package/tests/unit/components/Core/Modal.spec.ts +518 -518
  161. package/tests/unit/components/Core/NotificationBubble.spec.ts +606 -606
  162. package/tests/unit/components/Core/OtpInput.spec.ts +708 -708
  163. package/tests/unit/components/Core/PhoneInput.spec.ts +757 -619
  164. package/tests/unit/components/Core/RadioGroup.spec.ts +318 -0
  165. package/tests/unit/components/Core/Select.spec.ts +712 -712
  166. package/tests/unit/components/Core/SelectAutocomplete.spec.ts +361 -0
  167. package/tests/unit/components/Core/TextArea.spec.ts +565 -565
  168. package/tests/unit/components/Core/TickBox.spec.ts +836 -836
  169. package/tests/unit/components/Core/Tile.spec.ts +286 -0
  170. package/tests/unit/components/DateInput/DateInput.spec.ts +128 -0
  171. package/tests/unit/components/ErrorPage/ErrorPage.spec.ts +313 -313
  172. package/tests/unit/components/ErrorPage/ErrorPageLogo.spec.ts +153 -153
  173. package/tests/unit/components/IconBullet/IconBullet.spec.ts +356 -356
  174. package/tests/unit/components/IconBullet/IconBulletList.spec.ts +371 -371
  175. package/tests/unit/components/Icons/AdvanceAppointments.spec.ts +186 -186
  176. package/tests/unit/components/Icons/Audio/CloudFailed.spec.ts +108 -108
  177. package/tests/unit/components/Icons/Audio/CloudSaved.spec.ts +149 -149
  178. package/tests/unit/components/Icons/Audio/Delete.spec.ts +158 -158
  179. package/tests/unit/components/Icons/Audio/Pause.spec.ts +208 -208
  180. package/tests/unit/components/Icons/Audio/Play.spec.ts +217 -217
  181. package/tests/unit/components/Icons/CalendarNotification.spec.ts +193 -193
  182. package/tests/unit/components/Icons/Chair.spec.ts +241 -241
  183. package/tests/unit/components/Icons/ChairNotification.spec.ts +318 -318
  184. package/tests/unit/components/Icons/Circle.spec.ts +255 -255
  185. package/tests/unit/components/Icons/FavIcon.spec.ts +259 -259
  186. package/tests/unit/components/Icons/FilledCircle.spec.ts +274 -274
  187. package/tests/unit/components/Icons/Group3.spec.ts +362 -362
  188. package/tests/unit/components/Icons/Logo.spec.ts +229 -229
  189. package/tests/unit/components/Icons/MiniLogo.spec.ts +38 -38
  190. package/tests/unit/components/Icons/RingNotification.spec.ts +400 -400
  191. package/tests/unit/components/Icons/SolidArrowRight.spec.ts +49 -49
  192. package/tests/unit/components/Icons/calendar.spec.ts +293 -293
  193. package/tests/unit/components/Icons/checkbox.spec.ts +315 -315
  194. package/tests/unit/components/Icons/outlineChecked.spec.ts +441 -441
  195. package/tests/unit/components/Icons/play.spec.ts +315 -315
  196. package/tests/unit/components/Laboratory/AppointmentCard.spec.ts +167 -167
  197. package/tests/unit/components/Laboratory/ChatBoxImage.spec.ts +179 -179
  198. package/tests/unit/components/Laboratory/ChatMessage.spec.ts +263 -263
  199. package/tests/unit/components/Laboratory/ChatMessageBadge.spec.ts +282 -282
  200. package/tests/unit/components/Laboratory/ChatNotification.spec.ts +256 -256
  201. package/tests/unit/components/Laboratory/DocumentCard.spec.ts +228 -228
  202. package/tests/unit/components/Laboratory/DocumentCardItem.spec.ts +236 -236
  203. package/tests/unit/components/Laboratory/InfoCard.spec.ts +308 -308
  204. package/tests/unit/components/Laboratory/MainColumnsBar.spec.ts +251 -251
  205. package/tests/unit/components/Laboratory/ProgressCircle.spec.ts +290 -290
  206. package/tests/unit/components/Laboratory/ProgressLinear.spec.ts +275 -275
  207. package/tests/unit/components/Laboratory/SelectionColumnBar.spec.ts +288 -288
  208. package/tests/unit/components/Laboratory/StatusNotification.spec.ts +296 -296
  209. package/tests/unit/components/Laboratory/TagLabel.spec.ts +353 -353
  210. package/tests/unit/components/Laboratory/TagLabelGroup.spec.ts +377 -377
  211. package/tests/unit/components/Laboratory/TicketCard.spec.ts +351 -351
  212. package/tests/unit/components/Laboratory/TimeLineEvent.spec.ts +381 -381
  213. package/tests/unit/components/Laboratory/Timeline.spec.ts +419 -419
  214. package/tests/unit/components/Loader/Loader.spec.ts +197 -197
  215. package/tests/unit/components/MaintenanceBanner/MaintenanceBanner.spec.ts +302 -302
  216. package/tests/unit/constants/iconEnums.spec.ts +39 -39
  217. package/tests/unit/i18n/i18n.spec.ts +88 -88
  218. package/tests/unit/plugins/vuetify.spec.ts +182 -182
  219. package/tests/unit/setup.ts +237 -237
  220. package/tests/unit/src/components/index.spec.ts.skip +192 -192
  221. package/tests/unit/src/index.spec.ts.skip +182 -182
  222. package/tests/unit/src/main.spec.ts +111 -111
  223. package/tests/unit/utils/accessibility.spec.ts +318 -318
  224. package/tests/unit/utils/anamnese.spec.ts +531 -0
  225. package/tsconfig.json +26 -26
  226. package/vite.config.ts +29 -29
  227. package/vitest.config.ts +91 -91
@@ -0,0 +1,286 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import Tile from '@components/Tile/Tile.vue';
4
+
5
+ // Mock Banner component
6
+ vi.mock('@components/Banner/Banner.vue', () => ({
7
+ default: {
8
+ name: 'Banner',
9
+ template: '<div class="wl-banner" data-testid="banner">{{ text }}</div>',
10
+ props: ['text', 'color', 'bgColor', 'foldColor'],
11
+ },
12
+ }));
13
+
14
+ const mountTile = (props: Record<string, any> = {}, options: Record<string, any> = {}) => {
15
+ return mount(Tile, {
16
+ props,
17
+ ...options,
18
+ });
19
+ };
20
+
21
+ describe('Tile', () => {
22
+ beforeEach(() => {
23
+ vi.clearAllMocks();
24
+ });
25
+
26
+ // ─── Default Rendering ────────────────────────────────────────────
27
+ describe('Default Rendering', () => {
28
+ it('renders with data-testid="root"', () => {
29
+ const wrapper = mountTile();
30
+ expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
31
+ });
32
+
33
+ it('applies wl-tile CSS class', () => {
34
+ const wrapper = mountTile();
35
+ expect(wrapper.find('.wl-tile').exists()).toBe(true);
36
+ });
37
+
38
+ it('renders the body container', () => {
39
+ const wrapper = mountTile();
40
+ expect(wrapper.find('.wl-tile__body').exists()).toBe(true);
41
+ });
42
+
43
+ it('renders icon by default (showIcon=true)', () => {
44
+ const wrapper = mountTile();
45
+ expect(wrapper.find('.wl-tile__icon').exists()).toBe(true);
46
+ });
47
+
48
+ it('is focusable by default (tabindex=0)', () => {
49
+ const wrapper = mountTile();
50
+ expect(wrapper.find('[data-testid="root"]').attributes('tabindex')).toBe('0');
51
+ });
52
+ });
53
+
54
+ // ─── Title ────────────────────────────────────────────────────────
55
+ describe('Title', () => {
56
+ it('renders title when provided', () => {
57
+ const wrapper = mountTile({ title: 'My Tile' });
58
+ expect(wrapper.find('.wl-tile__title').text()).toBe('My Tile');
59
+ });
60
+
61
+ it('does not render title element when title is undefined', () => {
62
+ const wrapper = mountTile({ showTitle: false });
63
+ expect(wrapper.find('.wl-tile__title').exists()).toBe(false);
64
+ });
65
+
66
+ it('hides title when showTitle is false', () => {
67
+ const wrapper = mountTile({ title: 'Hidden', showTitle: false });
68
+ expect(wrapper.find('.wl-tile__title').exists()).toBe(false);
69
+ });
70
+ });
71
+
72
+ // ─── Description ──────────────────────────────────────────────────
73
+ describe('Description', () => {
74
+ it('renders description when provided', () => {
75
+ const wrapper = mountTile({ description: 'Tile description' });
76
+ expect(wrapper.find('.wl-tile__description').text()).toBe('Tile description');
77
+ });
78
+
79
+ it('does not render description element when not provided', () => {
80
+ const wrapper = mountTile();
81
+ expect(wrapper.find('.wl-tile__description').exists()).toBe(false);
82
+ });
83
+ });
84
+
85
+ // ─── Label (default, non-banner) ─────────────────────────────────
86
+ describe('Label (default)', () => {
87
+ it('renders label text when provided', () => {
88
+ const wrapper = mountTile({ label: 'Status: OK' });
89
+ expect(wrapper.find('.wl-tile__label-text').text()).toBe('Status: OK');
90
+ });
91
+
92
+ it('does not render label when not provided', () => {
93
+ const wrapper = mountTile();
94
+ expect(wrapper.find('.wl-tile__label').exists()).toBe(false);
95
+ });
96
+
97
+ it('hides label when showLabel is false', () => {
98
+ const wrapper = mountTile({ label: 'Hidden', showLabel: false });
99
+ expect(wrapper.find('.wl-tile__label').exists()).toBe(false);
100
+ });
101
+
102
+ it('does not show default label when useWLBanner is true', () => {
103
+ const wrapper = mountTile({ label: 'Banner', useWLBanner: true });
104
+ expect(wrapper.find('.wl-tile__label').exists()).toBe(false);
105
+ });
106
+ });
107
+
108
+ // ─── Banner Mode ──────────────────────────────────────────────────
109
+ describe('Banner Mode (useWLBanner)', () => {
110
+ it('renders Banner component when useWLBanner and label are set', () => {
111
+ const wrapper = mountTile({ useWLBanner: true, label: 'Tag' });
112
+ expect(wrapper.find('[data-testid="banner"]').exists()).toBe(true);
113
+ });
114
+
115
+ it('does not render Banner when label is missing', () => {
116
+ const wrapper = mountTile({ useWLBanner: true });
117
+ expect(wrapper.find('[data-testid="banner"]').exists()).toBe(false);
118
+ });
119
+
120
+ it('passes bannerColor to Banner', () => {
121
+ const wrapper = mountTile({
122
+ useWLBanner: true,
123
+ label: 'Tag',
124
+ bannerColor: 'green',
125
+ });
126
+ const banner = wrapper.findComponent({ name: 'Banner' });
127
+ expect(banner.props('color')).toBe('green');
128
+ });
129
+
130
+ it('passes custom bgColor/foldColor to Banner', () => {
131
+ const wrapper = mountTile({
132
+ useWLBanner: true,
133
+ label: 'Tag',
134
+ bannerBgColor: '#aaa',
135
+ bannerFoldColor: '#bbb',
136
+ });
137
+ const banner = wrapper.findComponent({ name: 'Banner' });
138
+ expect(banner.props('bgColor')).toBe('#aaa');
139
+ expect(banner.props('foldColor')).toBe('#bbb');
140
+ });
141
+ });
142
+
143
+ // ─── Icon ─────────────────────────────────────────────────────────
144
+ describe('Icon', () => {
145
+ it('renders icon by default', () => {
146
+ const wrapper = mountTile();
147
+ expect(wrapper.find('.wl-tile__icon').exists()).toBe(true);
148
+ });
149
+
150
+ it('hides icon when showIcon is false', () => {
151
+ const wrapper = mountTile({ showIcon: false });
152
+ expect(wrapper.find('.wl-tile__icon').exists()).toBe(false);
153
+ });
154
+
155
+ it('renders default icon (mdi-check-circle)', () => {
156
+ const wrapper = mountTile();
157
+ const icon = wrapper.find('.v-icon');
158
+ expect(icon.exists()).toBe(true);
159
+ });
160
+
161
+ it('uses custom icon when provided', () => {
162
+ const wrapper = mountTile({ icon: 'mdi-star' });
163
+ const icon = wrapper.find('.v-icon');
164
+ expect(icon.exists()).toBe(true);
165
+ });
166
+ });
167
+
168
+ // ─── Disabled State ───────────────────────────────────────────────
169
+ describe('Disabled State', () => {
170
+ it('adds wl-tile--disabled class', () => {
171
+ const wrapper = mountTile({ disabled: true });
172
+ expect(wrapper.find('.wl-tile--disabled').exists()).toBe(true);
173
+ });
174
+
175
+ it('sets tabindex to -1 when disabled', () => {
176
+ const wrapper = mountTile({ disabled: true });
177
+ expect(wrapper.find('[data-testid="root"]').attributes('tabindex')).toBe('-1');
178
+ });
179
+
180
+ it('does not add disabled class by default', () => {
181
+ const wrapper = mountTile();
182
+ expect(wrapper.find('.wl-tile--disabled').exists()).toBe(false);
183
+ });
184
+ });
185
+
186
+ // ─── Slots ────────────────────────────────────────────────────────
187
+ describe('Slots', () => {
188
+ it('renders default slot content', () => {
189
+ const wrapper = mountTile({}, {
190
+ slots: { default: '<div class="custom">Custom body</div>' },
191
+ });
192
+ expect(wrapper.find('.custom').text()).toBe('Custom body');
193
+ });
194
+
195
+ it('renders title slot', () => {
196
+ const wrapper = mountTile({}, {
197
+ slots: { title: '<span class="custom-title">Title slot</span>' },
198
+ });
199
+ expect(wrapper.find('.custom-title').text()).toBe('Title slot');
200
+ });
201
+
202
+ it('renders description slot', () => {
203
+ const wrapper = mountTile({}, {
204
+ slots: { description: '<p class="custom-desc">Desc slot</p>' },
205
+ });
206
+ expect(wrapper.find('.custom-desc').text()).toBe('Desc slot');
207
+ });
208
+
209
+ it('renders prepend slot', () => {
210
+ const wrapper = mountTile({}, {
211
+ slots: { prepend: '<div class="custom-prepend">Prepend</div>' },
212
+ });
213
+ expect(wrapper.find('.custom-prepend').text()).toBe('Prepend');
214
+ });
215
+
216
+ it('renders label slot', () => {
217
+ const wrapper = mountTile({}, {
218
+ slots: { label: '<span class="custom-label">Label slot</span>' },
219
+ });
220
+ expect(wrapper.find('.custom-label').text()).toBe('Label slot');
221
+ });
222
+ });
223
+
224
+ // ─── WCAG Accessibility ───────────────────────────────────────────
225
+ describe('WCAG Accessibility', () => {
226
+ it('sets explicit aria-label when provided (WCAG 4.1.2)', () => {
227
+ const wrapper = mountTile({ ariaLabel: 'Tile for status' });
228
+ expect(wrapper.find('[data-testid="root"]').attributes('aria-label')).toBe('Tile for status');
229
+ });
230
+
231
+ it('computes aria-label from title and label when ariaLabel not set', () => {
232
+ const wrapper = mountTile({ title: 'Check-up', label: 'Done' });
233
+ expect(wrapper.find('[data-testid="root"]').attributes('aria-label')).toBe('Check-up — Done');
234
+ });
235
+
236
+ it('uses only title in aria-label when label is missing', () => {
237
+ const wrapper = mountTile({ title: 'Check-up' });
238
+ expect(wrapper.find('[data-testid="root"]').attributes('aria-label')).toBe('Check-up');
239
+ });
240
+
241
+ it('uses only label in aria-label when title is missing', () => {
242
+ const wrapper = mountTile({ label: 'Done' });
243
+ expect(wrapper.find('[data-testid="root"]').attributes('aria-label')).toBe('Done');
244
+ });
245
+
246
+ it('has no aria-label when neither title, label, nor ariaLabel is set', () => {
247
+ const wrapper = mountTile();
248
+ expect(wrapper.find('[data-testid="root"]').attributes('aria-label')).toBeUndefined();
249
+ });
250
+
251
+ it('is focusable via keyboard (tabindex=0) (WCAG 2.1.1)', () => {
252
+ const wrapper = mountTile();
253
+ expect(wrapper.find('[data-testid="root"]').attributes('tabindex')).toBe('0');
254
+ });
255
+
256
+ it('is not focusable when disabled (tabindex=-1)', () => {
257
+ const wrapper = mountTile({ disabled: true });
258
+ expect(wrapper.find('[data-testid="root"]').attributes('tabindex')).toBe('-1');
259
+ });
260
+ });
261
+
262
+ // ─── Edge Cases ───────────────────────────────────────────────────
263
+ describe('Edge Cases', () => {
264
+ it('handles all props simultaneously', () => {
265
+ const wrapper = mountTile({
266
+ title: 'Title',
267
+ description: 'Desc',
268
+ label: 'Label',
269
+ icon: 'mdi-star',
270
+ disabled: false,
271
+ useWLBanner: false,
272
+ });
273
+ expect(wrapper.find('.wl-tile__title').text()).toBe('Title');
274
+ expect(wrapper.find('.wl-tile__description').text()).toBe('Desc');
275
+ expect(wrapper.find('.wl-tile__label-text').text()).toBe('Label');
276
+ });
277
+
278
+ it('handles prop changes reactively', async () => {
279
+ const wrapper = mountTile({ title: 'Old' });
280
+ expect(wrapper.find('.wl-tile__title').text()).toBe('Old');
281
+
282
+ await wrapper.setProps({ title: 'New' });
283
+ expect(wrapper.find('.wl-tile__title').text()).toBe('New');
284
+ });
285
+ });
286
+ });
@@ -770,4 +770,132 @@ describe('DateInput Edge Cases', () => {
770
770
 
771
771
  expect(wrapper.vm.inputState).toBe('error');
772
772
  });
773
+
774
+ // ─── External Error Prop ──────────────────────────────────────────
775
+ describe('External Error Prop', () => {
776
+ it('defaults error prop to false', () => {
777
+ wrapper = mountComponent();
778
+ expect(wrapper.vm.error).toBe(false);
779
+ });
780
+
781
+ it('accepts error prop', () => {
782
+ wrapper = mountComponent({ error: true });
783
+ expect(wrapper.vm.error).toBe(true);
784
+ });
785
+
786
+ it('passes error state to Input component', () => {
787
+ wrapper = mountComponent({ error: true });
788
+ const root = wrapper.find('[data-testid="root"]');
789
+ // The :error binding uses `inputState === 'error' || props.error`
790
+ expect(root.classes()).toContain('is-error');
791
+ });
792
+
793
+ it('external error does not override internal error', async () => {
794
+ wrapper = mountComponent({ error: true });
795
+ const input = wrapper.find('input');
796
+
797
+ // Type invalid date to trigger internal error
798
+ await input.setValue('99.99.9999');
799
+ await input.trigger('input');
800
+
801
+ // Internal error takes priority for the hint message
802
+ expect(wrapper.vm.inputState).toBe('error');
803
+ });
804
+
805
+ it('shows external error when no internal error', () => {
806
+ wrapper = mountComponent({ error: true });
807
+ // inputState is idle (no input), so error comes from external prop
808
+ expect(wrapper.vm.inputState).toBe('idle');
809
+ expect(wrapper.vm.error).toBe(true);
810
+ });
811
+
812
+ it('clears external error display when error prop becomes false', async () => {
813
+ wrapper = mountComponent({ error: true });
814
+ expect(wrapper.find('[data-testid="root"]').classes()).toContain('is-error');
815
+
816
+ await wrapper.setProps({ error: false });
817
+ // Without internal error, should not show error
818
+ expect(wrapper.find('[data-testid="root"]').classes()).not.toContain('is-error');
819
+ });
820
+ });
821
+
822
+ // ─── External Error Message ───────────────────────────────────────
823
+ describe('External Error Message', () => {
824
+ it('defaults errorMessage to empty string', () => {
825
+ wrapper = mountComponent();
826
+ expect(wrapper.vm.errorMessage).toBe('');
827
+ });
828
+
829
+ it('accepts errorMessage prop', () => {
830
+ wrapper = mountComponent({ errorMessage: 'Pflichtfeld' });
831
+ expect(wrapper.vm.errorMessage).toBe('Pflichtfeld');
832
+ });
833
+
834
+ it('shows errorMessage in hint when external error is active and no internal error', () => {
835
+ wrapper = mountComponent({ error: true, errorMessage: 'Pflichtfeld' });
836
+ // In default layout, hint = inputState === 'error' ? inputMessage : (props.error ? props.errorMessage : '')
837
+ // inputState is 'idle', so hint = props.errorMessage = 'Pflichtfeld'
838
+ const hint = wrapper.find('.hint-text');
839
+ if (hint.exists()) {
840
+ expect(hint.text()).toBe('Pflichtfeld');
841
+ }
842
+ });
843
+
844
+ it('internal error message takes priority over errorMessage', async () => {
845
+ wrapper = mountComponent({ error: true, errorMessage: 'Pflichtfeld' });
846
+ const input = wrapper.find('input');
847
+
848
+ // Trigger internal error
849
+ await input.setValue('99.99.9999');
850
+ await input.trigger('input');
851
+
852
+ // Internal error message should display, not errorMessage
853
+ expect(wrapper.vm.inputState).toBe('error');
854
+ expect(wrapper.vm.inputMessage).not.toBe('');
855
+ });
856
+ });
857
+
858
+ // ─── Validity Emission with External Error ────────────────────────
859
+ describe('Validity Emission with External Error', () => {
860
+ it('emits update:valid as false when external error is set even if date is valid', async () => {
861
+ wrapper = mountComponent({ error: true });
862
+ const input = wrapper.find('input');
863
+
864
+ // Enter a valid date
865
+ await input.setValue('12052024');
866
+ await input.trigger('input');
867
+
868
+ // inputState will be 'success', but external error means validity should be false
869
+ const validEvents = wrapper.emitted('update:valid') || [];
870
+ const lastEvent = validEvents[validEvents.length - 1];
871
+ expect(lastEvent?.[0]).toBe(false);
872
+ });
873
+
874
+ it('emits update:valid as true when date is valid and no external error', async () => {
875
+ wrapper = mountComponent({ error: false });
876
+ const input = wrapper.find('input');
877
+
878
+ await input.setValue('12052024');
879
+ await input.trigger('input');
880
+
881
+ const validEvents = wrapper.emitted('update:valid') || [];
882
+ const lastEvent = validEvents[validEvents.length - 1];
883
+ expect(lastEvent?.[0]).toBe(true);
884
+ });
885
+ });
886
+
887
+ // ─── hideDetails with External Error ──────────────────────────────
888
+ describe('hideDetails with External Error', () => {
889
+ it('shows details when external error is active', () => {
890
+ wrapper = mountComponent({ error: true, errorMessage: 'Required' });
891
+ // hideDetails should be false when error is showing
892
+ // In default layout: !(inputState === 'error' || props.error)
893
+ // = !(false || true) = false → details shown
894
+ });
895
+
896
+ it('hides details when no error', () => {
897
+ wrapper = mountComponent({ error: false });
898
+ // !(false || false) = true → details hidden
899
+ });
900
+ });
773
901
  });