@zap-wunschlachen/wl-shared-components 1.0.37 → 1.0.38
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 -43
- 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 +232 -232
- package/src/assets/css/variables.css +109 -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/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/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 +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/StagingBanner/StagingBanner.css +19 -0
- package/src/components/StagingBanner/StagingBanner.vue +82 -0
- 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 -28
- 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 +109 -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
- package/public/audio/test.aac +0 -0
|
@@ -1,709 +1,709 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { mount, flushPromises } from '@vue/test-utils';
|
|
3
|
-
import { nextTick } from 'vue';
|
|
4
|
-
import OtpInput from '@components/OtpInput/OtpInput.vue';
|
|
5
|
-
|
|
6
|
-
// Mock vue-i18n
|
|
7
|
-
const mockT = vi.fn().mockImplementation((key: string, params?: Record<string, any>) => {
|
|
8
|
-
const translations: Record<string, string> = {
|
|
9
|
-
'wl.otp_input.enter_sms_code': 'Enter SMS Code',
|
|
10
|
-
'wl.otp_input.code_was_sent': 'Code sent to number {phoneNumber}',
|
|
11
|
-
'wl.otp_input.confirm': 'Confirm',
|
|
12
|
-
'wl.otp_input.resend': 'Resend'
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
let translation = translations[key] || key;
|
|
16
|
-
|
|
17
|
-
// Handle parameter interpolation
|
|
18
|
-
if (params) {
|
|
19
|
-
Object.keys(params).forEach(param => {
|
|
20
|
-
translation = translation.replace(`{${param}}`, params[param]);
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return translation;
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
vi.mock('vue-i18n', () => ({
|
|
28
|
-
useI18n: () => ({
|
|
29
|
-
t: mockT
|
|
30
|
-
})
|
|
31
|
-
}));
|
|
32
|
-
|
|
33
|
-
describe('OtpInput', () => {
|
|
34
|
-
const defaultProps = {
|
|
35
|
-
phoneNumber: '+1234567890',
|
|
36
|
-
submitFn: vi.fn(),
|
|
37
|
-
resendFn: vi.fn()
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
beforeEach(() => {
|
|
41
|
-
vi.clearAllMocks();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// Test default behavior and rendering
|
|
45
|
-
describe('Default Behavior', () => {
|
|
46
|
-
it('renders with required props', () => {
|
|
47
|
-
const wrapper = mount(OtpInput, {
|
|
48
|
-
props: defaultProps
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
52
|
-
expect(wrapper.find('.wl-otp-input').exists()).toBe(true);
|
|
53
|
-
expect(wrapper.find('.title-container').exists()).toBe(true);
|
|
54
|
-
expect(wrapper.find('.input-container').exists()).toBe(true);
|
|
55
|
-
expect(wrapper.find('.message-container').exists()).toBe(true);
|
|
56
|
-
expect(wrapper.find('.button-container').exists()).toBe(true);
|
|
57
|
-
expect(wrapper.vm.phoneNumber).toBe('+1234567890');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('displays phone number in title', () => {
|
|
61
|
-
const wrapper = mount(OtpInput, {
|
|
62
|
-
props: defaultProps
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
expect(wrapper.find('.title-container p').text()).toContain('+1234567890');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('has data-testid for testing', () => {
|
|
69
|
-
const wrapper = mount(OtpInput, {
|
|
70
|
-
props: defaultProps
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('initializes with default state', () => {
|
|
77
|
-
const wrapper = mount(OtpInput, {
|
|
78
|
-
props: defaultProps
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
expect(wrapper.vm.internalValue).toBe('');
|
|
82
|
-
expect(wrapper.vm.localOtpState).toBe('default');
|
|
83
|
-
expect(wrapper.vm.message).toBe('');
|
|
84
|
-
expect(wrapper.vm.loading).toBe(false);
|
|
85
|
-
expect(wrapper.vm.disableSubmitBtn).toBe(false);
|
|
86
|
-
expect(wrapper.vm.disableResendBtn).toBe(false);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Test i18n functionality
|
|
91
|
-
describe('Internationalization', () => {
|
|
92
|
-
it('uses i18n for title text', () => {
|
|
93
|
-
const wrapper = mount(OtpInput, {
|
|
94
|
-
props: defaultProps
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
expect(mockT).toHaveBeenCalledWith('wl.otp_input.enter_sms_code');
|
|
98
|
-
expect(wrapper.find('h2').text()).toBe('Enter SMS Code');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('uses i18n for button labels', () => {
|
|
102
|
-
const wrapper = mount(OtpInput, {
|
|
103
|
-
props: defaultProps
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
expect(mockT).toHaveBeenCalledWith('wl.otp_input.confirm');
|
|
107
|
-
expect(mockT).toHaveBeenCalledWith('wl.otp_input.resend');
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('displays phone number in description text', () => {
|
|
111
|
-
const wrapper = mount(OtpInput, {
|
|
112
|
-
props: {
|
|
113
|
-
...defaultProps,
|
|
114
|
-
phoneNumber: '+9876543210'
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
expect(wrapper.find('.title-container p').text()).toContain('+9876543210');
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// Test validation states
|
|
123
|
-
describe('Validation States', () => {
|
|
124
|
-
it('handles default state', () => {
|
|
125
|
-
const wrapper = mount(OtpInput, {
|
|
126
|
-
props: {
|
|
127
|
-
...defaultProps,
|
|
128
|
-
otpState: 'default'
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
expect(wrapper.vm.state).toBe('default');
|
|
133
|
-
expect(wrapper.vm.error).toBe(false);
|
|
134
|
-
expect(wrapper.vm.statusMessageStyle.visibility).toBe('hidden');
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it('handles error state', () => {
|
|
138
|
-
const wrapper = mount(OtpInput, {
|
|
139
|
-
props: {
|
|
140
|
-
...defaultProps,
|
|
141
|
-
otpState: 'error',
|
|
142
|
-
message: 'Invalid code'
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
expect(wrapper.vm.state).toBe('error');
|
|
147
|
-
expect(wrapper.vm.error).toBe(true);
|
|
148
|
-
expect(wrapper.vm.statusMessageStyle.visibility).toBe('visible');
|
|
149
|
-
expect(wrapper.vm.statusMessageStyle.color).toBe('var(--Error-Red-0) !important');
|
|
150
|
-
expect(wrapper.find('p[role="alert"]').text()).toBe('Invalid code');
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('handles success state', () => {
|
|
154
|
-
const wrapper = mount(OtpInput, {
|
|
155
|
-
props: {
|
|
156
|
-
...defaultProps,
|
|
157
|
-
otpState: 'success',
|
|
158
|
-
message: 'Code verified'
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
expect(wrapper.vm.state).toBe('success');
|
|
163
|
-
expect(wrapper.vm.error).toBe(false);
|
|
164
|
-
expect(wrapper.vm.statusMessageStyle.visibility).toBe('visible');
|
|
165
|
-
expect(wrapper.vm.statusMessageStyle.color).toBe('var(--Success-Green-0) !important');
|
|
166
|
-
expect(wrapper.find('p[role="status"]').text()).toBe('Code verified');
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('updates state when otpState prop changes', async () => {
|
|
170
|
-
const wrapper = mount(OtpInput, {
|
|
171
|
-
props: {
|
|
172
|
-
...defaultProps,
|
|
173
|
-
otpState: 'default'
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
expect(wrapper.vm.state).toBe('default');
|
|
178
|
-
|
|
179
|
-
await wrapper.setProps({ otpState: 'error' });
|
|
180
|
-
|
|
181
|
-
expect(wrapper.vm.state).toBe('error');
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it('resets state to default when input changes', () => {
|
|
185
|
-
const wrapper = mount(OtpInput, {
|
|
186
|
-
props: {
|
|
187
|
-
...defaultProps,
|
|
188
|
-
otpState: 'error'
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
expect(wrapper.vm.state).toBe('error');
|
|
193
|
-
|
|
194
|
-
wrapper.vm.onResetError();
|
|
195
|
-
|
|
196
|
-
expect(wrapper.vm.localOtpState).toBe('default');
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
// Test v-model functionality
|
|
201
|
-
describe('v-model', () => {
|
|
202
|
-
it('initializes internalValue as empty string', () => {
|
|
203
|
-
const wrapper = mount(OtpInput, {
|
|
204
|
-
props: defaultProps
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
expect(wrapper.vm.internalValue).toBe('');
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it('emits update:modelValue when internalValue changes', async () => {
|
|
211
|
-
const wrapper = mount(OtpInput, {
|
|
212
|
-
props: defaultProps
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
wrapper.vm.internalValue = '1234';
|
|
216
|
-
await nextTick();
|
|
217
|
-
|
|
218
|
-
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
|
|
219
|
-
expect(wrapper.emitted('update:modelValue')?.[1]).toEqual(['1234']); // Index 1 because immediate: true causes first emission
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it('emits update:modelValue immediately on mount', () => {
|
|
223
|
-
const wrapper = mount(OtpInput, {
|
|
224
|
-
props: defaultProps
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
|
|
228
|
-
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['']);
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
// Test button functionality
|
|
233
|
-
describe('Button Functionality', () => {
|
|
234
|
-
it('calls submitFn when submit button is clicked', async () => {
|
|
235
|
-
const submitFn = vi.fn();
|
|
236
|
-
const wrapper = mount(OtpInput, {
|
|
237
|
-
props: {
|
|
238
|
-
...defaultProps,
|
|
239
|
-
submitFn
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
wrapper.vm.onSubmit();
|
|
244
|
-
|
|
245
|
-
expect(submitFn).toHaveBeenCalled();
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it('calls resendFn and clears input when resend button is clicked', async () => {
|
|
249
|
-
const resendFn = vi.fn();
|
|
250
|
-
const wrapper = mount(OtpInput, {
|
|
251
|
-
props: {
|
|
252
|
-
...defaultProps,
|
|
253
|
-
resendFn
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
wrapper.vm.internalValue = '1234';
|
|
258
|
-
wrapper.vm.onResend();
|
|
259
|
-
|
|
260
|
-
expect(wrapper.vm.internalValue).toBe('');
|
|
261
|
-
expect(resendFn).toHaveBeenCalled();
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
it('disables submit button when disableSubmitBtn is true', () => {
|
|
265
|
-
const wrapper = mount(OtpInput, {
|
|
266
|
-
props: {
|
|
267
|
-
...defaultProps,
|
|
268
|
-
disableSubmitBtn: true
|
|
269
|
-
}
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
expect(wrapper.vm.disableSubmitBtn).toBe(true);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('disables resend button when disableResendBtn is true', () => {
|
|
276
|
-
const wrapper = mount(OtpInput, {
|
|
277
|
-
props: {
|
|
278
|
-
...defaultProps,
|
|
279
|
-
disableResendBtn: true
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
expect(wrapper.vm.disableResendBtn).toBe(true);
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
// Test loading state
|
|
288
|
-
describe('Loading State', () => {
|
|
289
|
-
it('handles loading prop', () => {
|
|
290
|
-
const wrapper = mount(OtpInput, {
|
|
291
|
-
props: {
|
|
292
|
-
...defaultProps,
|
|
293
|
-
loading: true
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
expect(wrapper.vm.loading).toBe(true);
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
it('passes loading to v-otp-input', () => {
|
|
301
|
-
const wrapper = mount(OtpInput, {
|
|
302
|
-
props: {
|
|
303
|
-
...defaultProps,
|
|
304
|
-
loading: true
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
// v-otp-input is stubbed, but we can verify the component renders
|
|
309
|
-
expect(wrapper.find('[data-testid="v-otp-input"]').exists()).toBe(true);
|
|
310
|
-
});
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
// Test color customization
|
|
314
|
-
describe('Color Customization', () => {
|
|
315
|
-
it('uses default colors', () => {
|
|
316
|
-
const wrapper = mount(OtpInput, {
|
|
317
|
-
props: defaultProps
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
expect(wrapper.vm.colors.error).toBe('var(--Error-Red-0)');
|
|
321
|
-
expect(wrapper.vm.colors.success).toBe('var(--Success-Green-0)');
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it('accepts custom colors', () => {
|
|
325
|
-
const customColors = {
|
|
326
|
-
error: '#FF0000',
|
|
327
|
-
success: '#00FF00'
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
const wrapper = mount(OtpInput, {
|
|
331
|
-
props: {
|
|
332
|
-
...defaultProps,
|
|
333
|
-
colors: customColors
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
expect(wrapper.vm.colors.error).toBe('#FF0000');
|
|
338
|
-
expect(wrapper.vm.colors.success).toBe('#00FF00');
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
it('applies custom error color to status message', () => {
|
|
342
|
-
const wrapper = mount(OtpInput, {
|
|
343
|
-
props: {
|
|
344
|
-
...defaultProps,
|
|
345
|
-
otpState: 'error',
|
|
346
|
-
colors: { error: '#FF0000' }
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
expect(wrapper.vm.statusMessageStyle.color).toBe('#FF0000 !important');
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
it('applies custom success color to status message', () => {
|
|
354
|
-
const wrapper = mount(OtpInput, {
|
|
355
|
-
props: {
|
|
356
|
-
...defaultProps,
|
|
357
|
-
otpState: 'success',
|
|
358
|
-
colors: { success: '#00FF00' }
|
|
359
|
-
}
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
expect(wrapper.vm.statusMessageStyle.color).toBe('#00FF00 !important');
|
|
363
|
-
});
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
// Test computed properties
|
|
367
|
-
describe('Computed Properties', () => {
|
|
368
|
-
it('computes statusMessageStyle correctly for default state', () => {
|
|
369
|
-
const wrapper = mount(OtpInput, {
|
|
370
|
-
props: defaultProps
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
const style = wrapper.vm.statusMessageStyle;
|
|
374
|
-
expect(style.visibility).toBe('hidden');
|
|
375
|
-
expect(style.minHeight).toBe('1.5em');
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
it('computes statusMessageStyle correctly for error state', () => {
|
|
379
|
-
const wrapper = mount(OtpInput, {
|
|
380
|
-
props: {
|
|
381
|
-
...defaultProps,
|
|
382
|
-
otpState: 'error'
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
const style = wrapper.vm.statusMessageStyle;
|
|
387
|
-
expect(style.visibility).toBe('visible');
|
|
388
|
-
expect(style.color).toBe('var(--Error-Red-0) !important');
|
|
389
|
-
expect(style.minHeight).toBe('1.5em');
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
it('computes statusMessageStyle correctly for success state', () => {
|
|
393
|
-
const wrapper = mount(OtpInput, {
|
|
394
|
-
props: {
|
|
395
|
-
...defaultProps,
|
|
396
|
-
otpState: 'success'
|
|
397
|
-
}
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
const style = wrapper.vm.statusMessageStyle;
|
|
401
|
-
expect(style.visibility).toBe('visible');
|
|
402
|
-
expect(style.color).toBe('var(--Success-Green-0) !important');
|
|
403
|
-
expect(style.minHeight).toBe('1.5em');
|
|
404
|
-
});
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
// Test form submission
|
|
408
|
-
describe('Form Submission', () => {
|
|
409
|
-
it('prevents default form submission', async () => {
|
|
410
|
-
const submitFn = vi.fn();
|
|
411
|
-
const wrapper = mount(OtpInput, {
|
|
412
|
-
props: {
|
|
413
|
-
...defaultProps,
|
|
414
|
-
submitFn
|
|
415
|
-
}
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
const form = wrapper.find('form');
|
|
419
|
-
const preventDefault = vi.fn();
|
|
420
|
-
|
|
421
|
-
await form.trigger('submit', {
|
|
422
|
-
preventDefault
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
// Due to @submit.prevent, preventDefault should be called by Vue
|
|
426
|
-
// In actual usage, but in tests we can verify the form exists
|
|
427
|
-
expect(form.exists()).toBe(true);
|
|
428
|
-
});
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
// Test slots
|
|
432
|
-
describe('Slots', () => {
|
|
433
|
-
it('renders default buttons when no slot provided', () => {
|
|
434
|
-
const wrapper = mount(OtpInput, {
|
|
435
|
-
props: defaultProps
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
// Two Button components should be rendered
|
|
439
|
-
expect(wrapper.findAllComponents({ name: 'Button' })).toHaveLength(2);
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
it('renders custom buttons slot', () => {
|
|
443
|
-
const wrapper = mount(OtpInput, {
|
|
444
|
-
props: defaultProps,
|
|
445
|
-
slots: {
|
|
446
|
-
buttons: '<div data-testid="custom-buttons">Custom Buttons</div>'
|
|
447
|
-
}
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
expect(wrapper.find('[data-testid="custom-buttons"]').exists()).toBe(true);
|
|
451
|
-
expect(wrapper.find('[data-testid="custom-buttons"]').text()).toBe('Custom Buttons');
|
|
452
|
-
});
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
// Test accessibility
|
|
456
|
-
describe('Accessibility', () => {
|
|
457
|
-
it('has proper form labeling', () => {
|
|
458
|
-
const wrapper = mount(OtpInput, {
|
|
459
|
-
props: defaultProps
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
const form = wrapper.find('form');
|
|
463
|
-
expect(form.attributes('aria-labelledby')).toBe('page-heading');
|
|
464
|
-
|
|
465
|
-
const heading = wrapper.find('#page-heading');
|
|
466
|
-
expect(heading.exists()).toBe(true);
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
it('links form to error message when in error state', () => {
|
|
470
|
-
const wrapper = mount(OtpInput, {
|
|
471
|
-
props: {
|
|
472
|
-
...defaultProps,
|
|
473
|
-
otpState: 'error'
|
|
474
|
-
}
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
const form = wrapper.find('form');
|
|
478
|
-
expect(form.attributes('aria-describedby')).toBe('form-error');
|
|
479
|
-
|
|
480
|
-
const errorMessage = wrapper.find('#form-error');
|
|
481
|
-
expect(errorMessage.exists()).toBe(true);
|
|
482
|
-
expect(errorMessage.attributes('role')).toBe('alert');
|
|
483
|
-
expect(errorMessage.attributes('aria-live')).toBe('assertive');
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
it('has proper status message for success state', () => {
|
|
487
|
-
const wrapper = mount(OtpInput, {
|
|
488
|
-
props: {
|
|
489
|
-
...defaultProps,
|
|
490
|
-
otpState: 'success'
|
|
491
|
-
}
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
const statusMessage = wrapper.find('#form-success');
|
|
495
|
-
expect(statusMessage.exists()).toBe(true);
|
|
496
|
-
expect(statusMessage.attributes('role')).toBe('status');
|
|
497
|
-
expect(statusMessage.attributes('aria-live')).toBe('polite');
|
|
498
|
-
expect(statusMessage.attributes('aria-atomic')).toBe('true');
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
it('has proper form structure', () => {
|
|
502
|
-
const wrapper = mount(OtpInput, {
|
|
503
|
-
props: defaultProps
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
expect(wrapper.find('form').exists()).toBe(true);
|
|
507
|
-
expect(wrapper.find('h2').exists()).toBe(true);
|
|
508
|
-
expect(wrapper.find('[data-testid="v-otp-input"]').exists()).toBe(true);
|
|
509
|
-
});
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
// Test OTP input configuration
|
|
513
|
-
describe('OTP Input Configuration', () => {
|
|
514
|
-
it('configures v-otp-input with correct props', () => {
|
|
515
|
-
const wrapper = mount(OtpInput, {
|
|
516
|
-
props: {
|
|
517
|
-
...defaultProps,
|
|
518
|
-
loading: true,
|
|
519
|
-
otpState: 'error'
|
|
520
|
-
}
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
// v-otp-input is stubbed, but we can verify the component structure
|
|
524
|
-
expect(wrapper.find('[data-testid="v-otp-input"]').exists()).toBe(true);
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
it('sets autofocus on v-otp-input', () => {
|
|
528
|
-
const wrapper = mount(OtpInput, {
|
|
529
|
-
props: defaultProps
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
// With stubbed components, we verify the structure exists
|
|
533
|
-
expect(wrapper.find('[data-testid="v-otp-input"]').exists()).toBe(true);
|
|
534
|
-
});
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
// Test edge cases
|
|
538
|
-
describe('Edge Cases', () => {
|
|
539
|
-
it('handles empty phone number', () => {
|
|
540
|
-
const wrapper = mount(OtpInput, {
|
|
541
|
-
props: {
|
|
542
|
-
...defaultProps,
|
|
543
|
-
phoneNumber: ''
|
|
544
|
-
}
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
const descriptionText = wrapper.find('.title-container p').text();
|
|
548
|
-
expect(descriptionText).toBe('Code sent to number');
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
it('handles special characters in phone number', () => {
|
|
552
|
-
const wrapper = mount(OtpInput, {
|
|
553
|
-
props: {
|
|
554
|
-
...defaultProps,
|
|
555
|
-
phoneNumber: '+1 (234) 567-8900'
|
|
556
|
-
}
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
expect(wrapper.find('.title-container p').text()).toContain('+1 (234) 567-8900');
|
|
560
|
-
});
|
|
561
|
-
|
|
562
|
-
it('handles undefined message', () => {
|
|
563
|
-
const wrapper = mount(OtpInput, {
|
|
564
|
-
props: {
|
|
565
|
-
...defaultProps,
|
|
566
|
-
message: undefined
|
|
567
|
-
}
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
// When undefined is passed, the component uses the default empty string
|
|
571
|
-
expect(wrapper.vm.message).toBe('');
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
it('handles empty message', () => {
|
|
575
|
-
const wrapper = mount(OtpInput, {
|
|
576
|
-
props: {
|
|
577
|
-
...defaultProps,
|
|
578
|
-
message: ''
|
|
579
|
-
}
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
expect(wrapper.vm.message).toBe('');
|
|
583
|
-
});
|
|
584
|
-
|
|
585
|
-
it('handles invalid otpState', () => {
|
|
586
|
-
const wrapper = mount(OtpInput, {
|
|
587
|
-
props: {
|
|
588
|
-
...defaultProps,
|
|
589
|
-
otpState: 'invalid' as any
|
|
590
|
-
}
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
expect(wrapper.vm.localOtpState).toBe('invalid');
|
|
594
|
-
// Should still work, just won't match enum values
|
|
595
|
-
});
|
|
596
|
-
});
|
|
597
|
-
|
|
598
|
-
// Test event handling
|
|
599
|
-
describe('Event Handling', () => {
|
|
600
|
-
it('triggers onResetError when OTP input changes', () => {
|
|
601
|
-
const wrapper = mount(OtpInput, {
|
|
602
|
-
props: {
|
|
603
|
-
...defaultProps,
|
|
604
|
-
otpState: 'error'
|
|
605
|
-
}
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
expect(wrapper.vm.state).toBe('error');
|
|
609
|
-
|
|
610
|
-
wrapper.vm.onResetError();
|
|
611
|
-
|
|
612
|
-
expect(wrapper.vm.localOtpState).toBe('default');
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
it('clears input and calls resend function on resend', () => {
|
|
616
|
-
const resendFn = vi.fn();
|
|
617
|
-
const wrapper = mount(OtpInput, {
|
|
618
|
-
props: {
|
|
619
|
-
...defaultProps,
|
|
620
|
-
resendFn
|
|
621
|
-
}
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
wrapper.vm.internalValue = '123456';
|
|
625
|
-
wrapper.vm.onResend();
|
|
626
|
-
|
|
627
|
-
expect(wrapper.vm.internalValue).toBe('');
|
|
628
|
-
expect(resendFn).toHaveBeenCalledTimes(1);
|
|
629
|
-
});
|
|
630
|
-
});
|
|
631
|
-
|
|
632
|
-
// Test complex scenarios
|
|
633
|
-
describe('Complex Scenarios', () => {
|
|
634
|
-
it('handles complete OTP workflow', async () => {
|
|
635
|
-
const submitFn = vi.fn();
|
|
636
|
-
const resendFn = vi.fn();
|
|
637
|
-
|
|
638
|
-
const wrapper = mount(OtpInput, {
|
|
639
|
-
props: {
|
|
640
|
-
phoneNumber: '+1234567890',
|
|
641
|
-
submitFn,
|
|
642
|
-
resendFn,
|
|
643
|
-
otpState: 'default',
|
|
644
|
-
message: ''
|
|
645
|
-
}
|
|
646
|
-
});
|
|
647
|
-
|
|
648
|
-
// Initial state
|
|
649
|
-
expect(wrapper.vm.state).toBe('default');
|
|
650
|
-
expect(wrapper.vm.internalValue).toBe('');
|
|
651
|
-
|
|
652
|
-
// User enters invalid code
|
|
653
|
-
wrapper.vm.internalValue = '1234';
|
|
654
|
-
await wrapper.setProps({
|
|
655
|
-
otpState: 'error',
|
|
656
|
-
message: 'Invalid code'
|
|
657
|
-
});
|
|
658
|
-
|
|
659
|
-
expect(wrapper.vm.state).toBe('error');
|
|
660
|
-
expect(wrapper.vm.error).toBe(true);
|
|
661
|
-
|
|
662
|
-
// User starts typing again (resets error)
|
|
663
|
-
wrapper.vm.onResetError();
|
|
664
|
-
expect(wrapper.vm.localOtpState).toBe('default');
|
|
665
|
-
|
|
666
|
-
// User enters correct code
|
|
667
|
-
wrapper.vm.internalValue = '5678';
|
|
668
|
-
await wrapper.setProps({
|
|
669
|
-
otpState: 'success',
|
|
670
|
-
message: 'Code verified'
|
|
671
|
-
});
|
|
672
|
-
|
|
673
|
-
expect(wrapper.vm.state).toBe('success');
|
|
674
|
-
expect(wrapper.vm.error).toBe(false);
|
|
675
|
-
|
|
676
|
-
// User submits
|
|
677
|
-
wrapper.vm.onSubmit();
|
|
678
|
-
expect(submitFn).toHaveBeenCalled();
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
it('works with all props configured', () => {
|
|
682
|
-
const wrapper = mount(OtpInput, {
|
|
683
|
-
props: {
|
|
684
|
-
phoneNumber: '+1 (555) 123-4567',
|
|
685
|
-
message: 'Test message',
|
|
686
|
-
otpState: 'success',
|
|
687
|
-
submitFn: vi.fn(),
|
|
688
|
-
resendFn: vi.fn(),
|
|
689
|
-
loading: true,
|
|
690
|
-
colors: {
|
|
691
|
-
error: '#FF0000',
|
|
692
|
-
success: '#00FF00'
|
|
693
|
-
},
|
|
694
|
-
disableSubmitBtn: true,
|
|
695
|
-
disableResendBtn: false
|
|
696
|
-
}
|
|
697
|
-
});
|
|
698
|
-
|
|
699
|
-
expect(wrapper.vm.phoneNumber).toBe('+1 (555) 123-4567');
|
|
700
|
-
expect(wrapper.vm.message).toBe('Test message');
|
|
701
|
-
expect(wrapper.vm.state).toBe('success');
|
|
702
|
-
expect(wrapper.vm.loading).toBe(true);
|
|
703
|
-
expect(wrapper.vm.colors.error).toBe('#FF0000');
|
|
704
|
-
expect(wrapper.vm.colors.success).toBe('#00FF00');
|
|
705
|
-
expect(wrapper.vm.disableSubmitBtn).toBe(true);
|
|
706
|
-
expect(wrapper.vm.disableResendBtn).toBe(false);
|
|
707
|
-
});
|
|
708
|
-
});
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { mount, flushPromises } from '@vue/test-utils';
|
|
3
|
+
import { nextTick } from 'vue';
|
|
4
|
+
import OtpInput from '@components/OtpInput/OtpInput.vue';
|
|
5
|
+
|
|
6
|
+
// Mock vue-i18n
|
|
7
|
+
const mockT = vi.fn().mockImplementation((key: string, params?: Record<string, any>) => {
|
|
8
|
+
const translations: Record<string, string> = {
|
|
9
|
+
'wl.otp_input.enter_sms_code': 'Enter SMS Code',
|
|
10
|
+
'wl.otp_input.code_was_sent': 'Code sent to number {phoneNumber}',
|
|
11
|
+
'wl.otp_input.confirm': 'Confirm',
|
|
12
|
+
'wl.otp_input.resend': 'Resend'
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let translation = translations[key] || key;
|
|
16
|
+
|
|
17
|
+
// Handle parameter interpolation
|
|
18
|
+
if (params) {
|
|
19
|
+
Object.keys(params).forEach(param => {
|
|
20
|
+
translation = translation.replace(`{${param}}`, params[param]);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return translation;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
vi.mock('vue-i18n', () => ({
|
|
28
|
+
useI18n: () => ({
|
|
29
|
+
t: mockT
|
|
30
|
+
})
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
describe('OtpInput', () => {
|
|
34
|
+
const defaultProps = {
|
|
35
|
+
phoneNumber: '+1234567890',
|
|
36
|
+
submitFn: vi.fn(),
|
|
37
|
+
resendFn: vi.fn()
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
vi.clearAllMocks();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Test default behavior and rendering
|
|
45
|
+
describe('Default Behavior', () => {
|
|
46
|
+
it('renders with required props', () => {
|
|
47
|
+
const wrapper = mount(OtpInput, {
|
|
48
|
+
props: defaultProps
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
52
|
+
expect(wrapper.find('.wl-otp-input').exists()).toBe(true);
|
|
53
|
+
expect(wrapper.find('.title-container').exists()).toBe(true);
|
|
54
|
+
expect(wrapper.find('.input-container').exists()).toBe(true);
|
|
55
|
+
expect(wrapper.find('.message-container').exists()).toBe(true);
|
|
56
|
+
expect(wrapper.find('.button-container').exists()).toBe(true);
|
|
57
|
+
expect(wrapper.vm.phoneNumber).toBe('+1234567890');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('displays phone number in title', () => {
|
|
61
|
+
const wrapper = mount(OtpInput, {
|
|
62
|
+
props: defaultProps
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(wrapper.find('.title-container p').text()).toContain('+1234567890');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('has data-testid for testing', () => {
|
|
69
|
+
const wrapper = mount(OtpInput, {
|
|
70
|
+
props: defaultProps
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('initializes with default state', () => {
|
|
77
|
+
const wrapper = mount(OtpInput, {
|
|
78
|
+
props: defaultProps
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(wrapper.vm.internalValue).toBe('');
|
|
82
|
+
expect(wrapper.vm.localOtpState).toBe('default');
|
|
83
|
+
expect(wrapper.vm.message).toBe('');
|
|
84
|
+
expect(wrapper.vm.loading).toBe(false);
|
|
85
|
+
expect(wrapper.vm.disableSubmitBtn).toBe(false);
|
|
86
|
+
expect(wrapper.vm.disableResendBtn).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Test i18n functionality
|
|
91
|
+
describe('Internationalization', () => {
|
|
92
|
+
it('uses i18n for title text', () => {
|
|
93
|
+
const wrapper = mount(OtpInput, {
|
|
94
|
+
props: defaultProps
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(mockT).toHaveBeenCalledWith('wl.otp_input.enter_sms_code');
|
|
98
|
+
expect(wrapper.find('h2').text()).toBe('Enter SMS Code');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('uses i18n for button labels', () => {
|
|
102
|
+
const wrapper = mount(OtpInput, {
|
|
103
|
+
props: defaultProps
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(mockT).toHaveBeenCalledWith('wl.otp_input.confirm');
|
|
107
|
+
expect(mockT).toHaveBeenCalledWith('wl.otp_input.resend');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('displays phone number in description text', () => {
|
|
111
|
+
const wrapper = mount(OtpInput, {
|
|
112
|
+
props: {
|
|
113
|
+
...defaultProps,
|
|
114
|
+
phoneNumber: '+9876543210'
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(wrapper.find('.title-container p').text()).toContain('+9876543210');
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Test validation states
|
|
123
|
+
describe('Validation States', () => {
|
|
124
|
+
it('handles default state', () => {
|
|
125
|
+
const wrapper = mount(OtpInput, {
|
|
126
|
+
props: {
|
|
127
|
+
...defaultProps,
|
|
128
|
+
otpState: 'default'
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(wrapper.vm.state).toBe('default');
|
|
133
|
+
expect(wrapper.vm.error).toBe(false);
|
|
134
|
+
expect(wrapper.vm.statusMessageStyle.visibility).toBe('hidden');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('handles error state', () => {
|
|
138
|
+
const wrapper = mount(OtpInput, {
|
|
139
|
+
props: {
|
|
140
|
+
...defaultProps,
|
|
141
|
+
otpState: 'error',
|
|
142
|
+
message: 'Invalid code'
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(wrapper.vm.state).toBe('error');
|
|
147
|
+
expect(wrapper.vm.error).toBe(true);
|
|
148
|
+
expect(wrapper.vm.statusMessageStyle.visibility).toBe('visible');
|
|
149
|
+
expect(wrapper.vm.statusMessageStyle.color).toBe('var(--Error-Red-0) !important');
|
|
150
|
+
expect(wrapper.find('p[role="alert"]').text()).toBe('Invalid code');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('handles success state', () => {
|
|
154
|
+
const wrapper = mount(OtpInput, {
|
|
155
|
+
props: {
|
|
156
|
+
...defaultProps,
|
|
157
|
+
otpState: 'success',
|
|
158
|
+
message: 'Code verified'
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(wrapper.vm.state).toBe('success');
|
|
163
|
+
expect(wrapper.vm.error).toBe(false);
|
|
164
|
+
expect(wrapper.vm.statusMessageStyle.visibility).toBe('visible');
|
|
165
|
+
expect(wrapper.vm.statusMessageStyle.color).toBe('var(--Success-Green-0) !important');
|
|
166
|
+
expect(wrapper.find('p[role="status"]').text()).toBe('Code verified');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('updates state when otpState prop changes', async () => {
|
|
170
|
+
const wrapper = mount(OtpInput, {
|
|
171
|
+
props: {
|
|
172
|
+
...defaultProps,
|
|
173
|
+
otpState: 'default'
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
expect(wrapper.vm.state).toBe('default');
|
|
178
|
+
|
|
179
|
+
await wrapper.setProps({ otpState: 'error' });
|
|
180
|
+
|
|
181
|
+
expect(wrapper.vm.state).toBe('error');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('resets state to default when input changes', () => {
|
|
185
|
+
const wrapper = mount(OtpInput, {
|
|
186
|
+
props: {
|
|
187
|
+
...defaultProps,
|
|
188
|
+
otpState: 'error'
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
expect(wrapper.vm.state).toBe('error');
|
|
193
|
+
|
|
194
|
+
wrapper.vm.onResetError();
|
|
195
|
+
|
|
196
|
+
expect(wrapper.vm.localOtpState).toBe('default');
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Test v-model functionality
|
|
201
|
+
describe('v-model', () => {
|
|
202
|
+
it('initializes internalValue as empty string', () => {
|
|
203
|
+
const wrapper = mount(OtpInput, {
|
|
204
|
+
props: defaultProps
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(wrapper.vm.internalValue).toBe('');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('emits update:modelValue when internalValue changes', async () => {
|
|
211
|
+
const wrapper = mount(OtpInput, {
|
|
212
|
+
props: defaultProps
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
wrapper.vm.internalValue = '1234';
|
|
216
|
+
await nextTick();
|
|
217
|
+
|
|
218
|
+
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
|
|
219
|
+
expect(wrapper.emitted('update:modelValue')?.[1]).toEqual(['1234']); // Index 1 because immediate: true causes first emission
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('emits update:modelValue immediately on mount', () => {
|
|
223
|
+
const wrapper = mount(OtpInput, {
|
|
224
|
+
props: defaultProps
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
|
|
228
|
+
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['']);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Test button functionality
|
|
233
|
+
describe('Button Functionality', () => {
|
|
234
|
+
it('calls submitFn when submit button is clicked', async () => {
|
|
235
|
+
const submitFn = vi.fn();
|
|
236
|
+
const wrapper = mount(OtpInput, {
|
|
237
|
+
props: {
|
|
238
|
+
...defaultProps,
|
|
239
|
+
submitFn
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
wrapper.vm.onSubmit();
|
|
244
|
+
|
|
245
|
+
expect(submitFn).toHaveBeenCalled();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('calls resendFn and clears input when resend button is clicked', async () => {
|
|
249
|
+
const resendFn = vi.fn();
|
|
250
|
+
const wrapper = mount(OtpInput, {
|
|
251
|
+
props: {
|
|
252
|
+
...defaultProps,
|
|
253
|
+
resendFn
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
wrapper.vm.internalValue = '1234';
|
|
258
|
+
wrapper.vm.onResend();
|
|
259
|
+
|
|
260
|
+
expect(wrapper.vm.internalValue).toBe('');
|
|
261
|
+
expect(resendFn).toHaveBeenCalled();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('disables submit button when disableSubmitBtn is true', () => {
|
|
265
|
+
const wrapper = mount(OtpInput, {
|
|
266
|
+
props: {
|
|
267
|
+
...defaultProps,
|
|
268
|
+
disableSubmitBtn: true
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
expect(wrapper.vm.disableSubmitBtn).toBe(true);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('disables resend button when disableResendBtn is true', () => {
|
|
276
|
+
const wrapper = mount(OtpInput, {
|
|
277
|
+
props: {
|
|
278
|
+
...defaultProps,
|
|
279
|
+
disableResendBtn: true
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
expect(wrapper.vm.disableResendBtn).toBe(true);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Test loading state
|
|
288
|
+
describe('Loading State', () => {
|
|
289
|
+
it('handles loading prop', () => {
|
|
290
|
+
const wrapper = mount(OtpInput, {
|
|
291
|
+
props: {
|
|
292
|
+
...defaultProps,
|
|
293
|
+
loading: true
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
expect(wrapper.vm.loading).toBe(true);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('passes loading to v-otp-input', () => {
|
|
301
|
+
const wrapper = mount(OtpInput, {
|
|
302
|
+
props: {
|
|
303
|
+
...defaultProps,
|
|
304
|
+
loading: true
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// v-otp-input is stubbed, but we can verify the component renders
|
|
309
|
+
expect(wrapper.find('[data-testid="v-otp-input"]').exists()).toBe(true);
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Test color customization
|
|
314
|
+
describe('Color Customization', () => {
|
|
315
|
+
it('uses default colors', () => {
|
|
316
|
+
const wrapper = mount(OtpInput, {
|
|
317
|
+
props: defaultProps
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
expect(wrapper.vm.colors.error).toBe('var(--Error-Red-0)');
|
|
321
|
+
expect(wrapper.vm.colors.success).toBe('var(--Success-Green-0)');
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('accepts custom colors', () => {
|
|
325
|
+
const customColors = {
|
|
326
|
+
error: '#FF0000',
|
|
327
|
+
success: '#00FF00'
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const wrapper = mount(OtpInput, {
|
|
331
|
+
props: {
|
|
332
|
+
...defaultProps,
|
|
333
|
+
colors: customColors
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
expect(wrapper.vm.colors.error).toBe('#FF0000');
|
|
338
|
+
expect(wrapper.vm.colors.success).toBe('#00FF00');
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('applies custom error color to status message', () => {
|
|
342
|
+
const wrapper = mount(OtpInput, {
|
|
343
|
+
props: {
|
|
344
|
+
...defaultProps,
|
|
345
|
+
otpState: 'error',
|
|
346
|
+
colors: { error: '#FF0000' }
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
expect(wrapper.vm.statusMessageStyle.color).toBe('#FF0000 !important');
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('applies custom success color to status message', () => {
|
|
354
|
+
const wrapper = mount(OtpInput, {
|
|
355
|
+
props: {
|
|
356
|
+
...defaultProps,
|
|
357
|
+
otpState: 'success',
|
|
358
|
+
colors: { success: '#00FF00' }
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
expect(wrapper.vm.statusMessageStyle.color).toBe('#00FF00 !important');
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Test computed properties
|
|
367
|
+
describe('Computed Properties', () => {
|
|
368
|
+
it('computes statusMessageStyle correctly for default state', () => {
|
|
369
|
+
const wrapper = mount(OtpInput, {
|
|
370
|
+
props: defaultProps
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
const style = wrapper.vm.statusMessageStyle;
|
|
374
|
+
expect(style.visibility).toBe('hidden');
|
|
375
|
+
expect(style.minHeight).toBe('1.5em');
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('computes statusMessageStyle correctly for error state', () => {
|
|
379
|
+
const wrapper = mount(OtpInput, {
|
|
380
|
+
props: {
|
|
381
|
+
...defaultProps,
|
|
382
|
+
otpState: 'error'
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
const style = wrapper.vm.statusMessageStyle;
|
|
387
|
+
expect(style.visibility).toBe('visible');
|
|
388
|
+
expect(style.color).toBe('var(--Error-Red-0) !important');
|
|
389
|
+
expect(style.minHeight).toBe('1.5em');
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('computes statusMessageStyle correctly for success state', () => {
|
|
393
|
+
const wrapper = mount(OtpInput, {
|
|
394
|
+
props: {
|
|
395
|
+
...defaultProps,
|
|
396
|
+
otpState: 'success'
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
const style = wrapper.vm.statusMessageStyle;
|
|
401
|
+
expect(style.visibility).toBe('visible');
|
|
402
|
+
expect(style.color).toBe('var(--Success-Green-0) !important');
|
|
403
|
+
expect(style.minHeight).toBe('1.5em');
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// Test form submission
|
|
408
|
+
describe('Form Submission', () => {
|
|
409
|
+
it('prevents default form submission', async () => {
|
|
410
|
+
const submitFn = vi.fn();
|
|
411
|
+
const wrapper = mount(OtpInput, {
|
|
412
|
+
props: {
|
|
413
|
+
...defaultProps,
|
|
414
|
+
submitFn
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const form = wrapper.find('form');
|
|
419
|
+
const preventDefault = vi.fn();
|
|
420
|
+
|
|
421
|
+
await form.trigger('submit', {
|
|
422
|
+
preventDefault
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// Due to @submit.prevent, preventDefault should be called by Vue
|
|
426
|
+
// In actual usage, but in tests we can verify the form exists
|
|
427
|
+
expect(form.exists()).toBe(true);
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Test slots
|
|
432
|
+
describe('Slots', () => {
|
|
433
|
+
it('renders default buttons when no slot provided', () => {
|
|
434
|
+
const wrapper = mount(OtpInput, {
|
|
435
|
+
props: defaultProps
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Two Button components should be rendered
|
|
439
|
+
expect(wrapper.findAllComponents({ name: 'Button' })).toHaveLength(2);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('renders custom buttons slot', () => {
|
|
443
|
+
const wrapper = mount(OtpInput, {
|
|
444
|
+
props: defaultProps,
|
|
445
|
+
slots: {
|
|
446
|
+
buttons: '<div data-testid="custom-buttons">Custom Buttons</div>'
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
expect(wrapper.find('[data-testid="custom-buttons"]').exists()).toBe(true);
|
|
451
|
+
expect(wrapper.find('[data-testid="custom-buttons"]').text()).toBe('Custom Buttons');
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// Test accessibility
|
|
456
|
+
describe('Accessibility', () => {
|
|
457
|
+
it('has proper form labeling', () => {
|
|
458
|
+
const wrapper = mount(OtpInput, {
|
|
459
|
+
props: defaultProps
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
const form = wrapper.find('form');
|
|
463
|
+
expect(form.attributes('aria-labelledby')).toBe('page-heading');
|
|
464
|
+
|
|
465
|
+
const heading = wrapper.find('#page-heading');
|
|
466
|
+
expect(heading.exists()).toBe(true);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it('links form to error message when in error state', () => {
|
|
470
|
+
const wrapper = mount(OtpInput, {
|
|
471
|
+
props: {
|
|
472
|
+
...defaultProps,
|
|
473
|
+
otpState: 'error'
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const form = wrapper.find('form');
|
|
478
|
+
expect(form.attributes('aria-describedby')).toBe('form-error');
|
|
479
|
+
|
|
480
|
+
const errorMessage = wrapper.find('#form-error');
|
|
481
|
+
expect(errorMessage.exists()).toBe(true);
|
|
482
|
+
expect(errorMessage.attributes('role')).toBe('alert');
|
|
483
|
+
expect(errorMessage.attributes('aria-live')).toBe('assertive');
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('has proper status message for success state', () => {
|
|
487
|
+
const wrapper = mount(OtpInput, {
|
|
488
|
+
props: {
|
|
489
|
+
...defaultProps,
|
|
490
|
+
otpState: 'success'
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
const statusMessage = wrapper.find('#form-success');
|
|
495
|
+
expect(statusMessage.exists()).toBe(true);
|
|
496
|
+
expect(statusMessage.attributes('role')).toBe('status');
|
|
497
|
+
expect(statusMessage.attributes('aria-live')).toBe('polite');
|
|
498
|
+
expect(statusMessage.attributes('aria-atomic')).toBe('true');
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it('has proper form structure', () => {
|
|
502
|
+
const wrapper = mount(OtpInput, {
|
|
503
|
+
props: defaultProps
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
expect(wrapper.find('form').exists()).toBe(true);
|
|
507
|
+
expect(wrapper.find('h2').exists()).toBe(true);
|
|
508
|
+
expect(wrapper.find('[data-testid="v-otp-input"]').exists()).toBe(true);
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// Test OTP input configuration
|
|
513
|
+
describe('OTP Input Configuration', () => {
|
|
514
|
+
it('configures v-otp-input with correct props', () => {
|
|
515
|
+
const wrapper = mount(OtpInput, {
|
|
516
|
+
props: {
|
|
517
|
+
...defaultProps,
|
|
518
|
+
loading: true,
|
|
519
|
+
otpState: 'error'
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
// v-otp-input is stubbed, but we can verify the component structure
|
|
524
|
+
expect(wrapper.find('[data-testid="v-otp-input"]').exists()).toBe(true);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it('sets autofocus on v-otp-input', () => {
|
|
528
|
+
const wrapper = mount(OtpInput, {
|
|
529
|
+
props: defaultProps
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
// With stubbed components, we verify the structure exists
|
|
533
|
+
expect(wrapper.find('[data-testid="v-otp-input"]').exists()).toBe(true);
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// Test edge cases
|
|
538
|
+
describe('Edge Cases', () => {
|
|
539
|
+
it('handles empty phone number', () => {
|
|
540
|
+
const wrapper = mount(OtpInput, {
|
|
541
|
+
props: {
|
|
542
|
+
...defaultProps,
|
|
543
|
+
phoneNumber: ''
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
const descriptionText = wrapper.find('.title-container p').text();
|
|
548
|
+
expect(descriptionText).toBe('Code sent to number');
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it('handles special characters in phone number', () => {
|
|
552
|
+
const wrapper = mount(OtpInput, {
|
|
553
|
+
props: {
|
|
554
|
+
...defaultProps,
|
|
555
|
+
phoneNumber: '+1 (234) 567-8900'
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
expect(wrapper.find('.title-container p').text()).toContain('+1 (234) 567-8900');
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it('handles undefined message', () => {
|
|
563
|
+
const wrapper = mount(OtpInput, {
|
|
564
|
+
props: {
|
|
565
|
+
...defaultProps,
|
|
566
|
+
message: undefined
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
// When undefined is passed, the component uses the default empty string
|
|
571
|
+
expect(wrapper.vm.message).toBe('');
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it('handles empty message', () => {
|
|
575
|
+
const wrapper = mount(OtpInput, {
|
|
576
|
+
props: {
|
|
577
|
+
...defaultProps,
|
|
578
|
+
message: ''
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
expect(wrapper.vm.message).toBe('');
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
it('handles invalid otpState', () => {
|
|
586
|
+
const wrapper = mount(OtpInput, {
|
|
587
|
+
props: {
|
|
588
|
+
...defaultProps,
|
|
589
|
+
otpState: 'invalid' as any
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
expect(wrapper.vm.localOtpState).toBe('invalid');
|
|
594
|
+
// Should still work, just won't match enum values
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
// Test event handling
|
|
599
|
+
describe('Event Handling', () => {
|
|
600
|
+
it('triggers onResetError when OTP input changes', () => {
|
|
601
|
+
const wrapper = mount(OtpInput, {
|
|
602
|
+
props: {
|
|
603
|
+
...defaultProps,
|
|
604
|
+
otpState: 'error'
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
expect(wrapper.vm.state).toBe('error');
|
|
609
|
+
|
|
610
|
+
wrapper.vm.onResetError();
|
|
611
|
+
|
|
612
|
+
expect(wrapper.vm.localOtpState).toBe('default');
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
it('clears input and calls resend function on resend', () => {
|
|
616
|
+
const resendFn = vi.fn();
|
|
617
|
+
const wrapper = mount(OtpInput, {
|
|
618
|
+
props: {
|
|
619
|
+
...defaultProps,
|
|
620
|
+
resendFn
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
wrapper.vm.internalValue = '123456';
|
|
625
|
+
wrapper.vm.onResend();
|
|
626
|
+
|
|
627
|
+
expect(wrapper.vm.internalValue).toBe('');
|
|
628
|
+
expect(resendFn).toHaveBeenCalledTimes(1);
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
// Test complex scenarios
|
|
633
|
+
describe('Complex Scenarios', () => {
|
|
634
|
+
it('handles complete OTP workflow', async () => {
|
|
635
|
+
const submitFn = vi.fn();
|
|
636
|
+
const resendFn = vi.fn();
|
|
637
|
+
|
|
638
|
+
const wrapper = mount(OtpInput, {
|
|
639
|
+
props: {
|
|
640
|
+
phoneNumber: '+1234567890',
|
|
641
|
+
submitFn,
|
|
642
|
+
resendFn,
|
|
643
|
+
otpState: 'default',
|
|
644
|
+
message: ''
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
// Initial state
|
|
649
|
+
expect(wrapper.vm.state).toBe('default');
|
|
650
|
+
expect(wrapper.vm.internalValue).toBe('');
|
|
651
|
+
|
|
652
|
+
// User enters invalid code
|
|
653
|
+
wrapper.vm.internalValue = '1234';
|
|
654
|
+
await wrapper.setProps({
|
|
655
|
+
otpState: 'error',
|
|
656
|
+
message: 'Invalid code'
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
expect(wrapper.vm.state).toBe('error');
|
|
660
|
+
expect(wrapper.vm.error).toBe(true);
|
|
661
|
+
|
|
662
|
+
// User starts typing again (resets error)
|
|
663
|
+
wrapper.vm.onResetError();
|
|
664
|
+
expect(wrapper.vm.localOtpState).toBe('default');
|
|
665
|
+
|
|
666
|
+
// User enters correct code
|
|
667
|
+
wrapper.vm.internalValue = '5678';
|
|
668
|
+
await wrapper.setProps({
|
|
669
|
+
otpState: 'success',
|
|
670
|
+
message: 'Code verified'
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
expect(wrapper.vm.state).toBe('success');
|
|
674
|
+
expect(wrapper.vm.error).toBe(false);
|
|
675
|
+
|
|
676
|
+
// User submits
|
|
677
|
+
wrapper.vm.onSubmit();
|
|
678
|
+
expect(submitFn).toHaveBeenCalled();
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
it('works with all props configured', () => {
|
|
682
|
+
const wrapper = mount(OtpInput, {
|
|
683
|
+
props: {
|
|
684
|
+
phoneNumber: '+1 (555) 123-4567',
|
|
685
|
+
message: 'Test message',
|
|
686
|
+
otpState: 'success',
|
|
687
|
+
submitFn: vi.fn(),
|
|
688
|
+
resendFn: vi.fn(),
|
|
689
|
+
loading: true,
|
|
690
|
+
colors: {
|
|
691
|
+
error: '#FF0000',
|
|
692
|
+
success: '#00FF00'
|
|
693
|
+
},
|
|
694
|
+
disableSubmitBtn: true,
|
|
695
|
+
disableResendBtn: false
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
expect(wrapper.vm.phoneNumber).toBe('+1 (555) 123-4567');
|
|
700
|
+
expect(wrapper.vm.message).toBe('Test message');
|
|
701
|
+
expect(wrapper.vm.state).toBe('success');
|
|
702
|
+
expect(wrapper.vm.loading).toBe(true);
|
|
703
|
+
expect(wrapper.vm.colors.error).toBe('#FF0000');
|
|
704
|
+
expect(wrapper.vm.colors.success).toBe('#00FF00');
|
|
705
|
+
expect(wrapper.vm.disableSubmitBtn).toBe(true);
|
|
706
|
+
expect(wrapper.vm.disableResendBtn).toBe(false);
|
|
707
|
+
});
|
|
708
|
+
});
|
|
709
709
|
});
|