@zap-wunschlachen/wl-shared-components 1.0.38 → 1.0.40
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 +33 -33
- 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 +234 -232
- package/src/assets/css/variables.css +112 -109
- 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 +118 -118
- package/src/components/Button/Button.vue +174 -174
- package/src/components/CheckBox/CheckBox.css +214 -214
- package/src/components/CheckBox/Checkbox.vue +138 -138
- 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 +38 -38
- package/src/components/EditField/EditField.css +19 -19
- package/src/components/EditField/EditField.vue +202 -202
- package/src/components/ErrorPage/ErrorPage.css +172 -172
- 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/{calendar.vue → Calendar.vue} +17 -17
- 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/RingNotification.vue +54 -54
- package/src/components/Icons/SolidArrowRight.vue +14 -14
- 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 +253 -253
- 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 +71 -71
- package/src/components/MaintenanceBanner/MaintenanceBanner.css +353 -353
- package/src/components/MaintenanceBanner/MaintenanceBanner.vue +127 -127
- package/src/components/MaintenanceBanner/MaintenanceIllustration.vue +54 -54
- 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 +151 -151
- 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 +316 -316
- 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/accessibility.css +218 -218
- package/src/components/index.ts +29 -29
- 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 +141 -141
- 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 +116 -109
- 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/accessibility/component-a11y.spec.ts +469 -469
- package/tests/unit/components/Accordion/AccordionGroup.spec.ts +228 -228
- package/tests/unit/components/Accordion/AccordionGroup.spec.ts.skip +342 -342
- package/tests/unit/components/Accordion/AccordionItem.spec.ts +292 -292
- package/tests/unit/components/Accordion/AccordionItem.spec.ts.skip +383 -383
- package/tests/unit/components/Appointment/AnamneseNotification.spec.ts +176 -176
- 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/Background/Background.spec.ts +177 -177
- 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/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 +61 -61
- 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/Logo.spec.ts +228 -228
- package/tests/unit/components/Icons/MiniLogo.spec.ts +38 -38
- package/tests/unit/components/Icons/RingNotification.spec.ts +393 -393
- package/tests/unit/components/Icons/SolidArrowRight.spec.ts +49 -49
- 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/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 +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/tests/unit/utils/accessibility.spec.ts +318 -318
- package/tsconfig.json +26 -26
- package/vite.config.ts +29 -29
- package/vitest.config.ts +83 -83
|
@@ -1,519 +1,519 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { mount } from '@vue/test-utils';
|
|
3
|
-
import Modal from '@components/Modal/Modal.vue';
|
|
4
|
-
|
|
5
|
-
describe('Modal', () => {
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
vi.clearAllMocks();
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
// Test default behavior and rendering
|
|
11
|
-
describe('Default Behavior', () => {
|
|
12
|
-
it('renders with default state', () => {
|
|
13
|
-
const wrapper = mount(Modal);
|
|
14
|
-
|
|
15
|
-
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
16
|
-
expect(wrapper.find('[data-testid="v-dialog"]').exists()).toBe(true);
|
|
17
|
-
expect(wrapper.find('.wl-modal').exists()).toBe(true);
|
|
18
|
-
expect(wrapper.vm.dialog).toBe(false);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('has data-testid for testing', () => {
|
|
22
|
-
const wrapper = mount(Modal);
|
|
23
|
-
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('applies wl-modal CSS class', () => {
|
|
27
|
-
const wrapper = mount(Modal);
|
|
28
|
-
expect(wrapper.find('.wl-modal').exists()).toBe(true);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('initializes dialog state as closed', () => {
|
|
32
|
-
const wrapper = mount(Modal);
|
|
33
|
-
expect(wrapper.vm.dialog).toBe(false);
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// Test teleport functionality
|
|
38
|
-
describe('Teleport', () => {
|
|
39
|
-
it('uses teleport to body', () => {
|
|
40
|
-
const wrapper = mount(Modal);
|
|
41
|
-
|
|
42
|
-
// With stubs, we can verify the teleport component exists
|
|
43
|
-
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('renders content inside teleport', () => {
|
|
47
|
-
const wrapper = mount(Modal, {
|
|
48
|
-
slots: {
|
|
49
|
-
content: '<div data-testid="modal-content">Modal Content</div>'
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
expect(wrapper.find('[data-testid="modal-content"]').exists()).toBe(true);
|
|
54
|
-
expect(wrapper.find('[data-testid="modal-content"]').text()).toBe('Modal Content');
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// Test dialog state management
|
|
59
|
-
describe('Dialog State Management', () => {
|
|
60
|
-
it('opens dialog when openDialog is called', () => {
|
|
61
|
-
const wrapper = mount(Modal);
|
|
62
|
-
|
|
63
|
-
expect(wrapper.vm.dialog).toBe(false);
|
|
64
|
-
|
|
65
|
-
wrapper.vm.openDialog();
|
|
66
|
-
|
|
67
|
-
expect(wrapper.vm.dialog).toBe(true);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('closes dialog when closeDialog is called', () => {
|
|
71
|
-
const wrapper = mount(Modal);
|
|
72
|
-
|
|
73
|
-
// First open the dialog
|
|
74
|
-
wrapper.vm.openDialog();
|
|
75
|
-
expect(wrapper.vm.dialog).toBe(true);
|
|
76
|
-
|
|
77
|
-
// Then close it
|
|
78
|
-
wrapper.vm.closeDialog();
|
|
79
|
-
|
|
80
|
-
expect(wrapper.vm.dialog).toBe(false);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('can toggle dialog state multiple times', () => {
|
|
84
|
-
const wrapper = mount(Modal);
|
|
85
|
-
|
|
86
|
-
// Initial state
|
|
87
|
-
expect(wrapper.vm.dialog).toBe(false);
|
|
88
|
-
|
|
89
|
-
// Open -> Close -> Open -> Close
|
|
90
|
-
wrapper.vm.openDialog();
|
|
91
|
-
expect(wrapper.vm.dialog).toBe(true);
|
|
92
|
-
|
|
93
|
-
wrapper.vm.closeDialog();
|
|
94
|
-
expect(wrapper.vm.dialog).toBe(false);
|
|
95
|
-
|
|
96
|
-
wrapper.vm.openDialog();
|
|
97
|
-
expect(wrapper.vm.dialog).toBe(true);
|
|
98
|
-
|
|
99
|
-
wrapper.vm.closeDialog();
|
|
100
|
-
expect(wrapper.vm.dialog).toBe(false);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('handles rapid open/close calls', () => {
|
|
104
|
-
const wrapper = mount(Modal);
|
|
105
|
-
|
|
106
|
-
// Rapid calls
|
|
107
|
-
wrapper.vm.openDialog();
|
|
108
|
-
wrapper.vm.closeDialog();
|
|
109
|
-
wrapper.vm.openDialog();
|
|
110
|
-
wrapper.vm.closeDialog();
|
|
111
|
-
wrapper.vm.openDialog();
|
|
112
|
-
|
|
113
|
-
expect(wrapper.vm.dialog).toBe(true);
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// Test exposed methods
|
|
118
|
-
describe('Exposed Methods', () => {
|
|
119
|
-
it('exposes openDialog method', () => {
|
|
120
|
-
const wrapper = mount(Modal);
|
|
121
|
-
|
|
122
|
-
expect(wrapper.vm.openDialog).toBeDefined();
|
|
123
|
-
expect(typeof wrapper.vm.openDialog).toBe('function');
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('exposes closeDialog method', () => {
|
|
127
|
-
const wrapper = mount(Modal);
|
|
128
|
-
|
|
129
|
-
expect(wrapper.vm.closeDialog).toBeDefined();
|
|
130
|
-
expect(typeof wrapper.vm.closeDialog).toBe('function');
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('can call exposed methods externally', () => {
|
|
134
|
-
const wrapper = mount(Modal);
|
|
135
|
-
|
|
136
|
-
// Access the component instance
|
|
137
|
-
const modalInstance = wrapper.vm;
|
|
138
|
-
|
|
139
|
-
expect(modalInstance.dialog).toBe(false);
|
|
140
|
-
|
|
141
|
-
modalInstance.openDialog();
|
|
142
|
-
expect(modalInstance.dialog).toBe(true);
|
|
143
|
-
|
|
144
|
-
modalInstance.closeDialog();
|
|
145
|
-
expect(modalInstance.dialog).toBe(false);
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// Test v-model binding
|
|
150
|
-
describe('v-model Binding', () => {
|
|
151
|
-
it('binds dialog state to v-dialog model', async () => {
|
|
152
|
-
const wrapper = mount(Modal);
|
|
153
|
-
|
|
154
|
-
// Initially closed
|
|
155
|
-
expect(wrapper.vm.dialog).toBe(false);
|
|
156
|
-
|
|
157
|
-
// Open dialog
|
|
158
|
-
wrapper.vm.openDialog();
|
|
159
|
-
await wrapper.vm.$nextTick();
|
|
160
|
-
|
|
161
|
-
expect(wrapper.vm.dialog).toBe(true);
|
|
162
|
-
// The v-dialog component should receive the updated model value
|
|
163
|
-
expect(wrapper.find('[data-testid="v-dialog"]').exists()).toBe(true);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('updates when dialog reactive ref changes', async () => {
|
|
167
|
-
const wrapper = mount(Modal);
|
|
168
|
-
|
|
169
|
-
// Directly modify the reactive ref
|
|
170
|
-
wrapper.vm.dialog = true;
|
|
171
|
-
await wrapper.vm.$nextTick();
|
|
172
|
-
|
|
173
|
-
expect(wrapper.vm.dialog).toBe(true);
|
|
174
|
-
|
|
175
|
-
wrapper.vm.dialog = false;
|
|
176
|
-
await wrapper.vm.$nextTick();
|
|
177
|
-
|
|
178
|
-
expect(wrapper.vm.dialog).toBe(false);
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
// Test content slot
|
|
183
|
-
describe('Content Slot', () => {
|
|
184
|
-
it('renders content slot', () => {
|
|
185
|
-
const wrapper = mount(Modal, {
|
|
186
|
-
slots: {
|
|
187
|
-
content: '<div data-testid="slot-content">Slot Content</div>'
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
expect(wrapper.find('[data-testid="slot-content"]').exists()).toBe(true);
|
|
192
|
-
expect(wrapper.find('[data-testid="slot-content"]').text()).toBe('Slot Content');
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it('passes closeDialog prop to content slot', () => {
|
|
196
|
-
const wrapper = mount(Modal, {
|
|
197
|
-
slots: {
|
|
198
|
-
content: `
|
|
199
|
-
<template #content="{ closeDialog }">
|
|
200
|
-
<div data-testid="slot-with-close">
|
|
201
|
-
<button @click="closeDialog" data-testid="close-button">
|
|
202
|
-
Close Modal
|
|
203
|
-
</button>
|
|
204
|
-
</div>
|
|
205
|
-
</template>
|
|
206
|
-
`
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
const closeButton = wrapper.find('[data-testid="close-button"]');
|
|
211
|
-
expect(closeButton.exists()).toBe(true);
|
|
212
|
-
expect(closeButton.text()).toBe('Close Modal');
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it('closeDialog slot prop closes the modal', async () => {
|
|
216
|
-
const wrapper = mount(Modal, {
|
|
217
|
-
slots: {
|
|
218
|
-
content: `
|
|
219
|
-
<template #content="{ closeDialog }">
|
|
220
|
-
<button @click="closeDialog" data-testid="close-btn">
|
|
221
|
-
Close
|
|
222
|
-
</button>
|
|
223
|
-
</template>
|
|
224
|
-
`
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// First open the modal
|
|
229
|
-
wrapper.vm.openDialog();
|
|
230
|
-
expect(wrapper.vm.dialog).toBe(true);
|
|
231
|
-
|
|
232
|
-
const closeButton = wrapper.find('[data-testid="close-btn"]');
|
|
233
|
-
await closeButton.trigger('click');
|
|
234
|
-
|
|
235
|
-
expect(wrapper.vm.dialog).toBe(false);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it('handles empty content slot', () => {
|
|
239
|
-
const wrapper = mount(Modal, {
|
|
240
|
-
slots: {
|
|
241
|
-
content: ''
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
expect(wrapper.find('[data-testid="v-dialog"]').exists()).toBe(true);
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it('handles complex content in slot', () => {
|
|
249
|
-
const wrapper = mount(Modal, {
|
|
250
|
-
slots: {
|
|
251
|
-
content: `
|
|
252
|
-
<template #content="{ closeDialog }">
|
|
253
|
-
<div data-testid="complex-content">
|
|
254
|
-
<header>
|
|
255
|
-
<h1>Modal Title</h1>
|
|
256
|
-
<button @click="closeDialog">×</button>
|
|
257
|
-
</header>
|
|
258
|
-
<main>
|
|
259
|
-
<p>Modal body content goes here</p>
|
|
260
|
-
<form>
|
|
261
|
-
<input type="text" placeholder="Name" />
|
|
262
|
-
<input type="email" placeholder="Email" />
|
|
263
|
-
</form>
|
|
264
|
-
</main>
|
|
265
|
-
<footer>
|
|
266
|
-
<button @click="closeDialog">Cancel</button>
|
|
267
|
-
<button type="submit">Save</button>
|
|
268
|
-
</footer>
|
|
269
|
-
</div>
|
|
270
|
-
</template>
|
|
271
|
-
`
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
expect(wrapper.find('[data-testid="complex-content"]').exists()).toBe(true);
|
|
276
|
-
expect(wrapper.find('header').exists()).toBe(true);
|
|
277
|
-
expect(wrapper.find('main').exists()).toBe(true);
|
|
278
|
-
expect(wrapper.find('footer').exists()).toBe(true);
|
|
279
|
-
expect(wrapper.find('form').exists()).toBe(true);
|
|
280
|
-
expect(wrapper.find('input[type="text"]').exists()).toBe(true);
|
|
281
|
-
expect(wrapper.find('input[type="email"]').exists()).toBe(true);
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
// Test Vuetify integration
|
|
286
|
-
describe('Vuetify Integration', () => {
|
|
287
|
-
it('passes width="auto" to v-dialog', () => {
|
|
288
|
-
const wrapper = mount(Modal);
|
|
289
|
-
|
|
290
|
-
const vDialog = wrapper.find('[data-testid="v-dialog"]');
|
|
291
|
-
expect(vDialog.exists()).toBe(true);
|
|
292
|
-
// With stubbed components, we can't directly test Vuetify props
|
|
293
|
-
// but we can verify the component renders correctly
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
it('properly binds v-model to v-dialog', async () => {
|
|
297
|
-
const wrapper = mount(Modal);
|
|
298
|
-
|
|
299
|
-
// Initially false
|
|
300
|
-
expect(wrapper.vm.dialog).toBe(false);
|
|
301
|
-
|
|
302
|
-
// Open dialog
|
|
303
|
-
wrapper.vm.openDialog();
|
|
304
|
-
await wrapper.vm.$nextTick();
|
|
305
|
-
|
|
306
|
-
// Verify binding
|
|
307
|
-
expect(wrapper.vm.dialog).toBe(true);
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
it('applies wl-modal class to v-dialog', () => {
|
|
311
|
-
const wrapper = mount(Modal);
|
|
312
|
-
|
|
313
|
-
expect(wrapper.find('.wl-modal').exists()).toBe(true);
|
|
314
|
-
});
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
// Test accessibility
|
|
318
|
-
describe('Accessibility', () => {
|
|
319
|
-
it('has data-testid for testing', () => {
|
|
320
|
-
const wrapper = mount(Modal);
|
|
321
|
-
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it('supports accessible modal patterns', () => {
|
|
325
|
-
const wrapper = mount(Modal, {
|
|
326
|
-
slots: {
|
|
327
|
-
content: `
|
|
328
|
-
<template #content="{ closeDialog }">
|
|
329
|
-
<div role="dialog" aria-labelledby="modal-title" aria-describedby="modal-description">
|
|
330
|
-
<h1 id="modal-title">Accessible Modal</h1>
|
|
331
|
-
<p id="modal-description">This modal follows accessibility guidelines</p>
|
|
332
|
-
<button @click="closeDialog" aria-label="Close modal">Close</button>
|
|
333
|
-
</div>
|
|
334
|
-
</template>
|
|
335
|
-
`
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
const dialogElement = wrapper.find('[role="dialog"]');
|
|
340
|
-
expect(dialogElement.exists()).toBe(true);
|
|
341
|
-
expect(dialogElement.attributes('aria-labelledby')).toBe('modal-title');
|
|
342
|
-
expect(dialogElement.attributes('aria-describedby')).toBe('modal-description');
|
|
343
|
-
|
|
344
|
-
const closeButton = wrapper.find('button');
|
|
345
|
-
expect(closeButton.attributes('aria-label')).toBe('Close modal');
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
it('supports keyboard interactions through slots', () => {
|
|
349
|
-
const wrapper = mount(Modal, {
|
|
350
|
-
slots: {
|
|
351
|
-
content: `
|
|
352
|
-
<template #content="{ closeDialog }">
|
|
353
|
-
<div data-testid="keyboard-modal">
|
|
354
|
-
<button @keydown.escape="closeDialog">
|
|
355
|
-
Content (Press Escape to close)
|
|
356
|
-
</button>
|
|
357
|
-
<button @click="closeDialog" @keydown.enter="closeDialog">
|
|
358
|
-
Close (Enter to activate)
|
|
359
|
-
</button>
|
|
360
|
-
</div>
|
|
361
|
-
</template>
|
|
362
|
-
`
|
|
363
|
-
}
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
expect(wrapper.find('[data-testid="keyboard-modal"]').exists()).toBe(true);
|
|
367
|
-
expect(wrapper.findAll('button')).toHaveLength(2);
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
// Test teleport and body mounting
|
|
372
|
-
describe('Teleport to Body', () => {
|
|
373
|
-
it('uses teleport with body target', () => {
|
|
374
|
-
const wrapper = mount(Modal);
|
|
375
|
-
|
|
376
|
-
// The teleport component should exist
|
|
377
|
-
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
it('renders modal content within teleport', () => {
|
|
381
|
-
const wrapper = mount(Modal, {
|
|
382
|
-
slots: {
|
|
383
|
-
content: '<div data-testid="teleported-content">Teleported to body</div>'
|
|
384
|
-
}
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
expect(wrapper.find('[data-testid="teleported-content"]').exists()).toBe(true);
|
|
388
|
-
expect(wrapper.find('[data-testid="teleported-content"]').text()).toBe('Teleported to body');
|
|
389
|
-
});
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
// Test edge cases
|
|
393
|
-
describe('Edge Cases', () => {
|
|
394
|
-
it('handles no slots provided', () => {
|
|
395
|
-
const wrapper = mount(Modal);
|
|
396
|
-
|
|
397
|
-
expect(wrapper.find('[data-testid="v-dialog"]').exists()).toBe(true);
|
|
398
|
-
expect(wrapper.vm.dialog).toBe(false);
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
it('handles direct dialog property manipulation', async () => {
|
|
402
|
-
const wrapper = mount(Modal);
|
|
403
|
-
|
|
404
|
-
// Directly set dialog property
|
|
405
|
-
wrapper.vm.dialog = true;
|
|
406
|
-
await wrapper.vm.$nextTick();
|
|
407
|
-
|
|
408
|
-
expect(wrapper.vm.dialog).toBe(true);
|
|
409
|
-
|
|
410
|
-
// Use method to close
|
|
411
|
-
wrapper.vm.closeDialog();
|
|
412
|
-
expect(wrapper.vm.dialog).toBe(false);
|
|
413
|
-
|
|
414
|
-
// Use method to open
|
|
415
|
-
wrapper.vm.openDialog();
|
|
416
|
-
expect(wrapper.vm.dialog).toBe(true);
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
it('maintains state consistency across operations', () => {
|
|
420
|
-
const wrapper = mount(Modal);
|
|
421
|
-
|
|
422
|
-
// Sequence of operations
|
|
423
|
-
expect(wrapper.vm.dialog).toBe(false);
|
|
424
|
-
|
|
425
|
-
wrapper.vm.openDialog();
|
|
426
|
-
expect(wrapper.vm.dialog).toBe(true);
|
|
427
|
-
|
|
428
|
-
wrapper.vm.openDialog(); // Should remain true
|
|
429
|
-
expect(wrapper.vm.dialog).toBe(true);
|
|
430
|
-
|
|
431
|
-
wrapper.vm.closeDialog();
|
|
432
|
-
expect(wrapper.vm.dialog).toBe(false);
|
|
433
|
-
|
|
434
|
-
wrapper.vm.closeDialog(); // Should remain false
|
|
435
|
-
expect(wrapper.vm.dialog).toBe(false);
|
|
436
|
-
});
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
// Test integration scenarios
|
|
440
|
-
describe('Integration Scenarios', () => {
|
|
441
|
-
it('works as a controlled modal', async () => {
|
|
442
|
-
const wrapper = mount(Modal, {
|
|
443
|
-
slots: {
|
|
444
|
-
content: `
|
|
445
|
-
<template #content="{ closeDialog }">
|
|
446
|
-
<div data-testid="controlled-modal">
|
|
447
|
-
<h2>Controlled Modal</h2>
|
|
448
|
-
<p>This modal can be controlled externally</p>
|
|
449
|
-
<button @click="closeDialog" data-testid="internal-close">
|
|
450
|
-
Internal Close
|
|
451
|
-
</button>
|
|
452
|
-
</div>
|
|
453
|
-
</template>
|
|
454
|
-
`
|
|
455
|
-
}
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
// External control - open
|
|
459
|
-
wrapper.vm.openDialog();
|
|
460
|
-
expect(wrapper.vm.dialog).toBe(true);
|
|
461
|
-
|
|
462
|
-
// Internal control - close
|
|
463
|
-
const internalCloseButton = wrapper.find('[data-testid="internal-close"]');
|
|
464
|
-
await internalCloseButton.trigger('click');
|
|
465
|
-
expect(wrapper.vm.dialog).toBe(false);
|
|
466
|
-
|
|
467
|
-
// External control - open again
|
|
468
|
-
wrapper.vm.openDialog();
|
|
469
|
-
expect(wrapper.vm.dialog).toBe(true);
|
|
470
|
-
|
|
471
|
-
// External control - close
|
|
472
|
-
wrapper.vm.closeDialog();
|
|
473
|
-
expect(wrapper.vm.dialog).toBe(false);
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
it('supports nested content and interactions', () => {
|
|
477
|
-
const wrapper = mount(Modal, {
|
|
478
|
-
slots: {
|
|
479
|
-
content: `
|
|
480
|
-
<template #content="{ closeDialog }">
|
|
481
|
-
<div data-testid="nested-modal">
|
|
482
|
-
<div class="modal-header">
|
|
483
|
-
<h1>Nested Modal Structure</h1>
|
|
484
|
-
</div>
|
|
485
|
-
<div class="modal-body">
|
|
486
|
-
<div class="section">
|
|
487
|
-
<h2>Section 1</h2>
|
|
488
|
-
<p>Content for section 1</p>
|
|
489
|
-
</div>
|
|
490
|
-
<div class="section">
|
|
491
|
-
<h2>Section 2</h2>
|
|
492
|
-
<p>Content for section 2</p>
|
|
493
|
-
<div class="nested-component">
|
|
494
|
-
<input type="text" placeholder="Nested input" />
|
|
495
|
-
<button type="button">Nested button</button>
|
|
496
|
-
</div>
|
|
497
|
-
</div>
|
|
498
|
-
</div>
|
|
499
|
-
<div class="modal-footer">
|
|
500
|
-
<button @click="closeDialog">Cancel</button>
|
|
501
|
-
<button type="submit">Submit</button>
|
|
502
|
-
</div>
|
|
503
|
-
</div>
|
|
504
|
-
</template>
|
|
505
|
-
`
|
|
506
|
-
}
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
expect(wrapper.find('[data-testid="nested-modal"]').exists()).toBe(true);
|
|
510
|
-
expect(wrapper.find('.modal-header').exists()).toBe(true);
|
|
511
|
-
expect(wrapper.find('.modal-body').exists()).toBe(true);
|
|
512
|
-
expect(wrapper.find('.modal-footer').exists()).toBe(true);
|
|
513
|
-
expect(wrapper.findAll('.section')).toHaveLength(2);
|
|
514
|
-
expect(wrapper.find('.nested-component').exists()).toBe(true);
|
|
515
|
-
expect(wrapper.find('input[type="text"]').exists()).toBe(true);
|
|
516
|
-
expect(wrapper.findAll('button')).toHaveLength(3); // nested button + cancel + submit
|
|
517
|
-
});
|
|
518
|
-
});
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import Modal from '@components/Modal/Modal.vue';
|
|
4
|
+
|
|
5
|
+
describe('Modal', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
vi.clearAllMocks();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// Test default behavior and rendering
|
|
11
|
+
describe('Default Behavior', () => {
|
|
12
|
+
it('renders with default state', () => {
|
|
13
|
+
const wrapper = mount(Modal);
|
|
14
|
+
|
|
15
|
+
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
16
|
+
expect(wrapper.find('[data-testid="v-dialog"]').exists()).toBe(true);
|
|
17
|
+
expect(wrapper.find('.wl-modal').exists()).toBe(true);
|
|
18
|
+
expect(wrapper.vm.dialog).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('has data-testid for testing', () => {
|
|
22
|
+
const wrapper = mount(Modal);
|
|
23
|
+
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('applies wl-modal CSS class', () => {
|
|
27
|
+
const wrapper = mount(Modal);
|
|
28
|
+
expect(wrapper.find('.wl-modal').exists()).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('initializes dialog state as closed', () => {
|
|
32
|
+
const wrapper = mount(Modal);
|
|
33
|
+
expect(wrapper.vm.dialog).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Test teleport functionality
|
|
38
|
+
describe('Teleport', () => {
|
|
39
|
+
it('uses teleport to body', () => {
|
|
40
|
+
const wrapper = mount(Modal);
|
|
41
|
+
|
|
42
|
+
// With stubs, we can verify the teleport component exists
|
|
43
|
+
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('renders content inside teleport', () => {
|
|
47
|
+
const wrapper = mount(Modal, {
|
|
48
|
+
slots: {
|
|
49
|
+
content: '<div data-testid="modal-content">Modal Content</div>'
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(wrapper.find('[data-testid="modal-content"]').exists()).toBe(true);
|
|
54
|
+
expect(wrapper.find('[data-testid="modal-content"]').text()).toBe('Modal Content');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Test dialog state management
|
|
59
|
+
describe('Dialog State Management', () => {
|
|
60
|
+
it('opens dialog when openDialog is called', () => {
|
|
61
|
+
const wrapper = mount(Modal);
|
|
62
|
+
|
|
63
|
+
expect(wrapper.vm.dialog).toBe(false);
|
|
64
|
+
|
|
65
|
+
wrapper.vm.openDialog();
|
|
66
|
+
|
|
67
|
+
expect(wrapper.vm.dialog).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('closes dialog when closeDialog is called', () => {
|
|
71
|
+
const wrapper = mount(Modal);
|
|
72
|
+
|
|
73
|
+
// First open the dialog
|
|
74
|
+
wrapper.vm.openDialog();
|
|
75
|
+
expect(wrapper.vm.dialog).toBe(true);
|
|
76
|
+
|
|
77
|
+
// Then close it
|
|
78
|
+
wrapper.vm.closeDialog();
|
|
79
|
+
|
|
80
|
+
expect(wrapper.vm.dialog).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('can toggle dialog state multiple times', () => {
|
|
84
|
+
const wrapper = mount(Modal);
|
|
85
|
+
|
|
86
|
+
// Initial state
|
|
87
|
+
expect(wrapper.vm.dialog).toBe(false);
|
|
88
|
+
|
|
89
|
+
// Open -> Close -> Open -> Close
|
|
90
|
+
wrapper.vm.openDialog();
|
|
91
|
+
expect(wrapper.vm.dialog).toBe(true);
|
|
92
|
+
|
|
93
|
+
wrapper.vm.closeDialog();
|
|
94
|
+
expect(wrapper.vm.dialog).toBe(false);
|
|
95
|
+
|
|
96
|
+
wrapper.vm.openDialog();
|
|
97
|
+
expect(wrapper.vm.dialog).toBe(true);
|
|
98
|
+
|
|
99
|
+
wrapper.vm.closeDialog();
|
|
100
|
+
expect(wrapper.vm.dialog).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('handles rapid open/close calls', () => {
|
|
104
|
+
const wrapper = mount(Modal);
|
|
105
|
+
|
|
106
|
+
// Rapid calls
|
|
107
|
+
wrapper.vm.openDialog();
|
|
108
|
+
wrapper.vm.closeDialog();
|
|
109
|
+
wrapper.vm.openDialog();
|
|
110
|
+
wrapper.vm.closeDialog();
|
|
111
|
+
wrapper.vm.openDialog();
|
|
112
|
+
|
|
113
|
+
expect(wrapper.vm.dialog).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Test exposed methods
|
|
118
|
+
describe('Exposed Methods', () => {
|
|
119
|
+
it('exposes openDialog method', () => {
|
|
120
|
+
const wrapper = mount(Modal);
|
|
121
|
+
|
|
122
|
+
expect(wrapper.vm.openDialog).toBeDefined();
|
|
123
|
+
expect(typeof wrapper.vm.openDialog).toBe('function');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('exposes closeDialog method', () => {
|
|
127
|
+
const wrapper = mount(Modal);
|
|
128
|
+
|
|
129
|
+
expect(wrapper.vm.closeDialog).toBeDefined();
|
|
130
|
+
expect(typeof wrapper.vm.closeDialog).toBe('function');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('can call exposed methods externally', () => {
|
|
134
|
+
const wrapper = mount(Modal);
|
|
135
|
+
|
|
136
|
+
// Access the component instance
|
|
137
|
+
const modalInstance = wrapper.vm;
|
|
138
|
+
|
|
139
|
+
expect(modalInstance.dialog).toBe(false);
|
|
140
|
+
|
|
141
|
+
modalInstance.openDialog();
|
|
142
|
+
expect(modalInstance.dialog).toBe(true);
|
|
143
|
+
|
|
144
|
+
modalInstance.closeDialog();
|
|
145
|
+
expect(modalInstance.dialog).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Test v-model binding
|
|
150
|
+
describe('v-model Binding', () => {
|
|
151
|
+
it('binds dialog state to v-dialog model', async () => {
|
|
152
|
+
const wrapper = mount(Modal);
|
|
153
|
+
|
|
154
|
+
// Initially closed
|
|
155
|
+
expect(wrapper.vm.dialog).toBe(false);
|
|
156
|
+
|
|
157
|
+
// Open dialog
|
|
158
|
+
wrapper.vm.openDialog();
|
|
159
|
+
await wrapper.vm.$nextTick();
|
|
160
|
+
|
|
161
|
+
expect(wrapper.vm.dialog).toBe(true);
|
|
162
|
+
// The v-dialog component should receive the updated model value
|
|
163
|
+
expect(wrapper.find('[data-testid="v-dialog"]').exists()).toBe(true);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('updates when dialog reactive ref changes', async () => {
|
|
167
|
+
const wrapper = mount(Modal);
|
|
168
|
+
|
|
169
|
+
// Directly modify the reactive ref
|
|
170
|
+
wrapper.vm.dialog = true;
|
|
171
|
+
await wrapper.vm.$nextTick();
|
|
172
|
+
|
|
173
|
+
expect(wrapper.vm.dialog).toBe(true);
|
|
174
|
+
|
|
175
|
+
wrapper.vm.dialog = false;
|
|
176
|
+
await wrapper.vm.$nextTick();
|
|
177
|
+
|
|
178
|
+
expect(wrapper.vm.dialog).toBe(false);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Test content slot
|
|
183
|
+
describe('Content Slot', () => {
|
|
184
|
+
it('renders content slot', () => {
|
|
185
|
+
const wrapper = mount(Modal, {
|
|
186
|
+
slots: {
|
|
187
|
+
content: '<div data-testid="slot-content">Slot Content</div>'
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
expect(wrapper.find('[data-testid="slot-content"]').exists()).toBe(true);
|
|
192
|
+
expect(wrapper.find('[data-testid="slot-content"]').text()).toBe('Slot Content');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('passes closeDialog prop to content slot', () => {
|
|
196
|
+
const wrapper = mount(Modal, {
|
|
197
|
+
slots: {
|
|
198
|
+
content: `
|
|
199
|
+
<template #content="{ closeDialog }">
|
|
200
|
+
<div data-testid="slot-with-close">
|
|
201
|
+
<button @click="closeDialog" data-testid="close-button">
|
|
202
|
+
Close Modal
|
|
203
|
+
</button>
|
|
204
|
+
</div>
|
|
205
|
+
</template>
|
|
206
|
+
`
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const closeButton = wrapper.find('[data-testid="close-button"]');
|
|
211
|
+
expect(closeButton.exists()).toBe(true);
|
|
212
|
+
expect(closeButton.text()).toBe('Close Modal');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('closeDialog slot prop closes the modal', async () => {
|
|
216
|
+
const wrapper = mount(Modal, {
|
|
217
|
+
slots: {
|
|
218
|
+
content: `
|
|
219
|
+
<template #content="{ closeDialog }">
|
|
220
|
+
<button @click="closeDialog" data-testid="close-btn">
|
|
221
|
+
Close
|
|
222
|
+
</button>
|
|
223
|
+
</template>
|
|
224
|
+
`
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// First open the modal
|
|
229
|
+
wrapper.vm.openDialog();
|
|
230
|
+
expect(wrapper.vm.dialog).toBe(true);
|
|
231
|
+
|
|
232
|
+
const closeButton = wrapper.find('[data-testid="close-btn"]');
|
|
233
|
+
await closeButton.trigger('click');
|
|
234
|
+
|
|
235
|
+
expect(wrapper.vm.dialog).toBe(false);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('handles empty content slot', () => {
|
|
239
|
+
const wrapper = mount(Modal, {
|
|
240
|
+
slots: {
|
|
241
|
+
content: ''
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
expect(wrapper.find('[data-testid="v-dialog"]').exists()).toBe(true);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('handles complex content in slot', () => {
|
|
249
|
+
const wrapper = mount(Modal, {
|
|
250
|
+
slots: {
|
|
251
|
+
content: `
|
|
252
|
+
<template #content="{ closeDialog }">
|
|
253
|
+
<div data-testid="complex-content">
|
|
254
|
+
<header>
|
|
255
|
+
<h1>Modal Title</h1>
|
|
256
|
+
<button @click="closeDialog">×</button>
|
|
257
|
+
</header>
|
|
258
|
+
<main>
|
|
259
|
+
<p>Modal body content goes here</p>
|
|
260
|
+
<form>
|
|
261
|
+
<input type="text" placeholder="Name" />
|
|
262
|
+
<input type="email" placeholder="Email" />
|
|
263
|
+
</form>
|
|
264
|
+
</main>
|
|
265
|
+
<footer>
|
|
266
|
+
<button @click="closeDialog">Cancel</button>
|
|
267
|
+
<button type="submit">Save</button>
|
|
268
|
+
</footer>
|
|
269
|
+
</div>
|
|
270
|
+
</template>
|
|
271
|
+
`
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
expect(wrapper.find('[data-testid="complex-content"]').exists()).toBe(true);
|
|
276
|
+
expect(wrapper.find('header').exists()).toBe(true);
|
|
277
|
+
expect(wrapper.find('main').exists()).toBe(true);
|
|
278
|
+
expect(wrapper.find('footer').exists()).toBe(true);
|
|
279
|
+
expect(wrapper.find('form').exists()).toBe(true);
|
|
280
|
+
expect(wrapper.find('input[type="text"]').exists()).toBe(true);
|
|
281
|
+
expect(wrapper.find('input[type="email"]').exists()).toBe(true);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Test Vuetify integration
|
|
286
|
+
describe('Vuetify Integration', () => {
|
|
287
|
+
it('passes width="auto" to v-dialog', () => {
|
|
288
|
+
const wrapper = mount(Modal);
|
|
289
|
+
|
|
290
|
+
const vDialog = wrapper.find('[data-testid="v-dialog"]');
|
|
291
|
+
expect(vDialog.exists()).toBe(true);
|
|
292
|
+
// With stubbed components, we can't directly test Vuetify props
|
|
293
|
+
// but we can verify the component renders correctly
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('properly binds v-model to v-dialog', async () => {
|
|
297
|
+
const wrapper = mount(Modal);
|
|
298
|
+
|
|
299
|
+
// Initially false
|
|
300
|
+
expect(wrapper.vm.dialog).toBe(false);
|
|
301
|
+
|
|
302
|
+
// Open dialog
|
|
303
|
+
wrapper.vm.openDialog();
|
|
304
|
+
await wrapper.vm.$nextTick();
|
|
305
|
+
|
|
306
|
+
// Verify binding
|
|
307
|
+
expect(wrapper.vm.dialog).toBe(true);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('applies wl-modal class to v-dialog', () => {
|
|
311
|
+
const wrapper = mount(Modal);
|
|
312
|
+
|
|
313
|
+
expect(wrapper.find('.wl-modal').exists()).toBe(true);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Test accessibility
|
|
318
|
+
describe('Accessibility', () => {
|
|
319
|
+
it('has data-testid for testing', () => {
|
|
320
|
+
const wrapper = mount(Modal);
|
|
321
|
+
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('supports accessible modal patterns', () => {
|
|
325
|
+
const wrapper = mount(Modal, {
|
|
326
|
+
slots: {
|
|
327
|
+
content: `
|
|
328
|
+
<template #content="{ closeDialog }">
|
|
329
|
+
<div role="dialog" aria-labelledby="modal-title" aria-describedby="modal-description">
|
|
330
|
+
<h1 id="modal-title">Accessible Modal</h1>
|
|
331
|
+
<p id="modal-description">This modal follows accessibility guidelines</p>
|
|
332
|
+
<button @click="closeDialog" aria-label="Close modal">Close</button>
|
|
333
|
+
</div>
|
|
334
|
+
</template>
|
|
335
|
+
`
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const dialogElement = wrapper.find('[role="dialog"]');
|
|
340
|
+
expect(dialogElement.exists()).toBe(true);
|
|
341
|
+
expect(dialogElement.attributes('aria-labelledby')).toBe('modal-title');
|
|
342
|
+
expect(dialogElement.attributes('aria-describedby')).toBe('modal-description');
|
|
343
|
+
|
|
344
|
+
const closeButton = wrapper.find('button');
|
|
345
|
+
expect(closeButton.attributes('aria-label')).toBe('Close modal');
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('supports keyboard interactions through slots', () => {
|
|
349
|
+
const wrapper = mount(Modal, {
|
|
350
|
+
slots: {
|
|
351
|
+
content: `
|
|
352
|
+
<template #content="{ closeDialog }">
|
|
353
|
+
<div data-testid="keyboard-modal">
|
|
354
|
+
<button @keydown.escape="closeDialog">
|
|
355
|
+
Content (Press Escape to close)
|
|
356
|
+
</button>
|
|
357
|
+
<button @click="closeDialog" @keydown.enter="closeDialog">
|
|
358
|
+
Close (Enter to activate)
|
|
359
|
+
</button>
|
|
360
|
+
</div>
|
|
361
|
+
</template>
|
|
362
|
+
`
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
expect(wrapper.find('[data-testid="keyboard-modal"]').exists()).toBe(true);
|
|
367
|
+
expect(wrapper.findAll('button')).toHaveLength(2);
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// Test teleport and body mounting
|
|
372
|
+
describe('Teleport to Body', () => {
|
|
373
|
+
it('uses teleport with body target', () => {
|
|
374
|
+
const wrapper = mount(Modal);
|
|
375
|
+
|
|
376
|
+
// The teleport component should exist
|
|
377
|
+
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('renders modal content within teleport', () => {
|
|
381
|
+
const wrapper = mount(Modal, {
|
|
382
|
+
slots: {
|
|
383
|
+
content: '<div data-testid="teleported-content">Teleported to body</div>'
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
expect(wrapper.find('[data-testid="teleported-content"]').exists()).toBe(true);
|
|
388
|
+
expect(wrapper.find('[data-testid="teleported-content"]').text()).toBe('Teleported to body');
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Test edge cases
|
|
393
|
+
describe('Edge Cases', () => {
|
|
394
|
+
it('handles no slots provided', () => {
|
|
395
|
+
const wrapper = mount(Modal);
|
|
396
|
+
|
|
397
|
+
expect(wrapper.find('[data-testid="v-dialog"]').exists()).toBe(true);
|
|
398
|
+
expect(wrapper.vm.dialog).toBe(false);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('handles direct dialog property manipulation', async () => {
|
|
402
|
+
const wrapper = mount(Modal);
|
|
403
|
+
|
|
404
|
+
// Directly set dialog property
|
|
405
|
+
wrapper.vm.dialog = true;
|
|
406
|
+
await wrapper.vm.$nextTick();
|
|
407
|
+
|
|
408
|
+
expect(wrapper.vm.dialog).toBe(true);
|
|
409
|
+
|
|
410
|
+
// Use method to close
|
|
411
|
+
wrapper.vm.closeDialog();
|
|
412
|
+
expect(wrapper.vm.dialog).toBe(false);
|
|
413
|
+
|
|
414
|
+
// Use method to open
|
|
415
|
+
wrapper.vm.openDialog();
|
|
416
|
+
expect(wrapper.vm.dialog).toBe(true);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('maintains state consistency across operations', () => {
|
|
420
|
+
const wrapper = mount(Modal);
|
|
421
|
+
|
|
422
|
+
// Sequence of operations
|
|
423
|
+
expect(wrapper.vm.dialog).toBe(false);
|
|
424
|
+
|
|
425
|
+
wrapper.vm.openDialog();
|
|
426
|
+
expect(wrapper.vm.dialog).toBe(true);
|
|
427
|
+
|
|
428
|
+
wrapper.vm.openDialog(); // Should remain true
|
|
429
|
+
expect(wrapper.vm.dialog).toBe(true);
|
|
430
|
+
|
|
431
|
+
wrapper.vm.closeDialog();
|
|
432
|
+
expect(wrapper.vm.dialog).toBe(false);
|
|
433
|
+
|
|
434
|
+
wrapper.vm.closeDialog(); // Should remain false
|
|
435
|
+
expect(wrapper.vm.dialog).toBe(false);
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// Test integration scenarios
|
|
440
|
+
describe('Integration Scenarios', () => {
|
|
441
|
+
it('works as a controlled modal', async () => {
|
|
442
|
+
const wrapper = mount(Modal, {
|
|
443
|
+
slots: {
|
|
444
|
+
content: `
|
|
445
|
+
<template #content="{ closeDialog }">
|
|
446
|
+
<div data-testid="controlled-modal">
|
|
447
|
+
<h2>Controlled Modal</h2>
|
|
448
|
+
<p>This modal can be controlled externally</p>
|
|
449
|
+
<button @click="closeDialog" data-testid="internal-close">
|
|
450
|
+
Internal Close
|
|
451
|
+
</button>
|
|
452
|
+
</div>
|
|
453
|
+
</template>
|
|
454
|
+
`
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// External control - open
|
|
459
|
+
wrapper.vm.openDialog();
|
|
460
|
+
expect(wrapper.vm.dialog).toBe(true);
|
|
461
|
+
|
|
462
|
+
// Internal control - close
|
|
463
|
+
const internalCloseButton = wrapper.find('[data-testid="internal-close"]');
|
|
464
|
+
await internalCloseButton.trigger('click');
|
|
465
|
+
expect(wrapper.vm.dialog).toBe(false);
|
|
466
|
+
|
|
467
|
+
// External control - open again
|
|
468
|
+
wrapper.vm.openDialog();
|
|
469
|
+
expect(wrapper.vm.dialog).toBe(true);
|
|
470
|
+
|
|
471
|
+
// External control - close
|
|
472
|
+
wrapper.vm.closeDialog();
|
|
473
|
+
expect(wrapper.vm.dialog).toBe(false);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('supports nested content and interactions', () => {
|
|
477
|
+
const wrapper = mount(Modal, {
|
|
478
|
+
slots: {
|
|
479
|
+
content: `
|
|
480
|
+
<template #content="{ closeDialog }">
|
|
481
|
+
<div data-testid="nested-modal">
|
|
482
|
+
<div class="modal-header">
|
|
483
|
+
<h1>Nested Modal Structure</h1>
|
|
484
|
+
</div>
|
|
485
|
+
<div class="modal-body">
|
|
486
|
+
<div class="section">
|
|
487
|
+
<h2>Section 1</h2>
|
|
488
|
+
<p>Content for section 1</p>
|
|
489
|
+
</div>
|
|
490
|
+
<div class="section">
|
|
491
|
+
<h2>Section 2</h2>
|
|
492
|
+
<p>Content for section 2</p>
|
|
493
|
+
<div class="nested-component">
|
|
494
|
+
<input type="text" placeholder="Nested input" />
|
|
495
|
+
<button type="button">Nested button</button>
|
|
496
|
+
</div>
|
|
497
|
+
</div>
|
|
498
|
+
</div>
|
|
499
|
+
<div class="modal-footer">
|
|
500
|
+
<button @click="closeDialog">Cancel</button>
|
|
501
|
+
<button type="submit">Submit</button>
|
|
502
|
+
</div>
|
|
503
|
+
</div>
|
|
504
|
+
</template>
|
|
505
|
+
`
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
expect(wrapper.find('[data-testid="nested-modal"]').exists()).toBe(true);
|
|
510
|
+
expect(wrapper.find('.modal-header').exists()).toBe(true);
|
|
511
|
+
expect(wrapper.find('.modal-body').exists()).toBe(true);
|
|
512
|
+
expect(wrapper.find('.modal-footer').exists()).toBe(true);
|
|
513
|
+
expect(wrapper.findAll('.section')).toHaveLength(2);
|
|
514
|
+
expect(wrapper.find('.nested-component').exists()).toBe(true);
|
|
515
|
+
expect(wrapper.find('input[type="text"]').exists()).toBe(true);
|
|
516
|
+
expect(wrapper.findAll('button')).toHaveLength(3); // nested button + cancel + submit
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
519
|
});
|