@zap-wunschlachen/wl-shared-components 1.0.25 → 1.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/playwright.yml +205 -205
- package/.github/workflows/static.yml +61 -61
- package/.github/workflows/update-snapshots.yml +37 -37
- package/.prettierrc +5 -5
- package/.storybook/main.ts +18 -18
- package/.storybook/preview.ts +37 -37
- package/.storybook/storyWrapper.vue +18 -18
- package/.storybook/withVuetifyTheme.decorator.ts +21 -21
- package/App.vue +34 -176
- package/README.md +56 -56
- package/heroicons.ts +75 -75
- package/index.html +19 -19
- package/package.json +67 -67
- package/playwright.config.ts +48 -48
- package/public/background.svg +60 -60
- package/public/style.css +187 -187
- package/public/technologies.svg +22 -22
- package/src/assets/css/base.css +235 -235
- package/src/assets/css/variables.css +107 -96
- package/src/components/Accordion/Accordion.css +59 -59
- package/src/components/Accordion/AccordionGroup.vue +51 -51
- package/src/components/Accordion/AccordionItem.vue +66 -66
- package/src/components/Appointment/Card/Actions.css +54 -54
- package/src/components/Appointment/Card/Actions.vue +99 -99
- package/src/components/Appointment/Card/AnamneseNotification.css +15 -15
- package/src/components/Appointment/Card/AnamneseNotification.vue +23 -23
- package/src/components/Appointment/Card/Card.css +80 -80
- package/src/components/Appointment/Card/Card.vue +93 -93
- package/src/components/Appointment/Card/Details.css +50 -50
- package/src/components/Appointment/Card/Details.vue +43 -43
- package/src/components/Audio/Audio.vue +187 -187
- package/src/components/Audio/Waveform.vue +117 -117
- package/src/components/Background/Background.css +39 -0
- package/src/components/Background/Background.vue +19 -0
- package/src/components/Background/WhiteCocoonBackground.vue +9 -0
- package/src/components/Background/WunschlachenBackground.vue +11 -0
- package/src/components/Button/Button.vue +119 -119
- package/src/components/CheckBox/CheckBox.css +185 -185
- package/src/components/CheckBox/Checkbox.vue +130 -130
- package/src/components/DateInput/DateInput.css +2 -2
- package/src/components/DateInput/DateInput.vue +262 -262
- package/src/components/Dialog/Dialog.css +6 -6
- package/src/components/Dialog/Dialog.vue +29 -29
- package/src/components/EditField/EditField.css +19 -19
- package/src/components/EditField/EditField.vue +202 -202
- package/src/components/ErrorPage/ErrorPage.css +124 -0
- package/src/components/ErrorPage/ErrorPage.vue +45 -0
- package/src/components/ErrorPage/ErrorPageLogo.vue +26 -0
- package/src/components/IconBullet/IconBullet.vue +86 -86
- package/src/components/IconBullet/IconBulletList.vue +41 -41
- package/src/components/Icons/AdvanceAppointments.vue +153 -153
- package/src/components/Icons/Audio/CloudFailed.vue +20 -20
- package/src/components/Icons/Audio/CloudSaved.vue +21 -21
- package/src/components/Icons/Audio/Delete.vue +15 -15
- package/src/components/Icons/Audio/Pause.vue +18 -18
- package/src/components/Icons/Audio/Play.vue +15 -15
- package/src/components/Icons/CalendarNotification.vue +126 -126
- package/src/components/Icons/Chair.vue +32 -32
- package/src/components/Icons/ChairNotification.vue +35 -35
- package/src/components/Icons/Circle.vue +66 -66
- package/src/components/Icons/FavIcon.vue +22 -22
- package/src/components/Icons/FilledCircle.vue +11 -11
- package/src/components/Icons/Group3.vue +46 -46
- package/src/components/Icons/Logo.vue +108 -0
- package/src/components/Icons/RingNotification.vue +54 -54
- package/src/components/Icons/SolidArrowRight.vue +14 -14
- package/src/components/Icons/calendar.vue +17 -17
- package/src/components/Icons/checkbox.vue +19 -19
- package/src/components/Icons/outlineChecked.vue +27 -27
- package/src/components/Icons/play.vue +5 -5
- package/src/components/Input/Input.css +187 -187
- package/src/components/Input/Input.vue +247 -247
- package/src/components/Laboratory/AppointmentCard/AppointmentCard.css +7 -7
- package/src/components/Laboratory/AppointmentCard/AppointmentCard.vue +116 -116
- package/src/components/Laboratory/ChatBoxImage/ChatBoxImage.vue +81 -81
- package/src/components/Laboratory/ChatMessage/ChatMessage.vue +113 -113
- package/src/components/Laboratory/ChatMessage/ChatMessageBadge.css +4 -4
- package/src/components/Laboratory/ChatMessage/ChatMessageBadge.vue +99 -99
- package/src/components/Laboratory/ChatNotification/ChatNotification.vue +130 -130
- package/src/components/Laboratory/DocumentCard/DocumentCard.css +3 -3
- package/src/components/Laboratory/DocumentCard/DocumentCard.vue +50 -50
- package/src/components/Laboratory/DocumentCard/DocumentCardItem.vue +53 -53
- package/src/components/Laboratory/InfoCard/InfoCard.vue +162 -162
- package/src/components/Laboratory/MainColumnsBar/MainColumnsBar.vue +102 -102
- package/src/components/Laboratory/ProgressCircle/ProgressCircle.vue +152 -152
- package/src/components/Laboratory/ProgressLinear/ProgressLinear.css +33 -33
- package/src/components/Laboratory/ProgressLinear/ProgressLinear.vue +75 -75
- package/src/components/Laboratory/SelectionColumnBar/SelectionColumnBar.vue +92 -92
- package/src/components/Laboratory/StatusNotification/StatusNotification.vue +49 -49
- package/src/components/Laboratory/TagLabel/TagLabel.vue +126 -126
- package/src/components/Laboratory/TagLabelGroup/TagLabelGroup.vue +97 -97
- package/src/components/Laboratory/TicketCard/TicketCard.css +3 -3
- package/src/components/Laboratory/TicketCard/TicketCard.vue +143 -143
- package/src/components/Laboratory/TimeLine/TimeLineEvent.css +18 -18
- package/src/components/Laboratory/TimeLine/TimeLineEvent.vue +119 -119
- package/src/components/Laboratory/TimeLine/Timeline.css +4 -4
- package/src/components/Laboratory/TimeLine/Timeline.vue +30 -30
- package/src/components/Loader/Loader.css +51 -51
- package/src/components/MaintenanceBanner/MaintenanceBanner.css +289 -0
- package/src/components/MaintenanceBanner/MaintenanceBanner.vue +127 -0
- package/src/components/MaintenanceBanner/MaintenanceIllustration.vue +54 -0
- package/src/components/Modal/Modal.css +5 -5
- package/src/components/Modal/Modal.vue +22 -22
- package/src/components/NotificationBubble/NotificationBubble.css +4 -4
- package/src/components/NotificationBubble/NotificationBubble.vue +90 -90
- package/src/components/OtpInput/OtpInput.css +39 -39
- package/src/components/OtpInput/OtpInput.vue +143 -143
- package/src/components/PhoneInput/PhoneInput.css +31 -31
- package/src/components/PhoneInput/PhoneInput.vue +113 -113
- package/src/components/Select/Select.css +150 -150
- package/src/components/Select/Select.vue +315 -315
- package/src/components/TextArea/TextArea.css +3 -3
- package/src/components/TextArea/TextArea.vue +126 -126
- package/src/components/TickBox/TickBox.css +49 -49
- package/src/components/TickBox/TickBox.vue +126 -126
- package/src/components/index.ts +26 -24
- package/src/constants/iconEnums.ts +3 -3
- package/src/i18n/i18n.ts +15 -15
- package/src/i18n/locales/de.json +30 -30
- package/src/i18n/locales/en.json +30 -30
- package/src/index.ts +34 -34
- package/src/main.ts +11 -11
- package/src/plugins/vuetify.ts +139 -131
- package/src/shims-vue.d.ts +10 -10
- package/src/stories/Accordion.stories.ts +650 -650
- package/src/stories/Audio.stories.ts +28 -28
- package/src/stories/Button.stories.ts +263 -263
- package/src/stories/CheckBox.stories.ts +348 -348
- package/src/stories/DateInput.stories.ts +53 -53
- package/src/stories/Dialog.stories.ts +147 -147
- package/src/stories/EditField.stories.ts +78 -78
- package/src/stories/IconBullet/IconBullet.stories.ts +201 -201
- package/src/stories/IconBullet/IconBulletList.stories.ts +275 -275
- package/src/stories/Input.stories.ts +351 -351
- package/src/stories/Laboratory/Cards/AppointmentCard/AppointmentCard.stories.ts +260 -260
- package/src/stories/Laboratory/Cards/DocumentCard/DocumentCard.stories.ts +176 -176
- package/src/stories/Laboratory/Cards/DocumentCard/DocumentCardItem.stories.ts +119 -119
- package/src/stories/Laboratory/Cards/InfoCard/InfoCard.stories.ts +320 -320
- package/src/stories/Laboratory/Cards/TicketCard/TicketCard.stories.ts +335 -335
- package/src/stories/Laboratory/Chat/ChatBoxImage.stories.ts +82 -82
- package/src/stories/Laboratory/Chat/ChatMessage.stories.ts +198 -198
- package/src/stories/Laboratory/Chat/ChatMessageBadge.stories.ts +204 -204
- package/src/stories/Laboratory/Chat/ChatNotification.stories.ts +144 -144
- package/src/stories/Laboratory/Chat/ProgressLinear.stories.ts +186 -186
- package/src/stories/Laboratory/Chat/StatusNotification.stories.ts +111 -111
- package/src/stories/Laboratory/MainColumnsBar.stories.ts +48 -48
- package/src/stories/Laboratory/ProgressCircle.stories.ts +261 -261
- package/src/stories/Laboratory/SelectionColumnBar.stories.ts +234 -234
- package/src/stories/Laboratory/TagLabel.stories.ts +418 -418
- package/src/stories/Laboratory/TagLabelGroup.stories.ts +234 -234
- package/src/stories/Laboratory/Timeline.stories.ts +403 -403
- package/src/stories/NotificationBubble.stories.ts +194 -194
- package/src/stories/OtpInput.stories.ts +100 -100
- package/src/stories/PhoneInput.stories.ts +52 -52
- package/src/stories/Select.stories.ts +419 -419
- package/src/stories/TextArea.stories.ts +112 -112
- package/src/stories/TickBox.stories.ts +294 -294
- package/src/stories/v-icon.stories.ts +91 -91
- package/src/utils/index.ts +106 -100
- package/src/vite-env.d.ts +1 -1
- package/tests/e2e/README.md +220 -220
- package/tests/e2e/accessibility.spec.ts +638 -638
- package/tests/e2e/accordion.spec.ts +42 -42
- package/tests/e2e/additional-components.spec.ts +437 -437
- package/tests/e2e/all-components.spec.ts +135 -135
- package/tests/e2e/appointment-card.spec.ts +816 -816
- package/tests/e2e/button-fixed.spec.ts +58 -58
- package/tests/e2e/button.spec.ts +76 -76
- package/tests/e2e/checkbox.spec.ts +50 -50
- package/tests/e2e/date-input.spec.ts +46 -46
- package/tests/e2e/debug.spec.ts +51 -51
- package/tests/e2e/dialog.spec.ts +58 -58
- package/tests/e2e/input.spec.ts +55 -55
- package/tests/e2e/laboratory-components.spec.ts +320 -320
- package/tests/e2e/otp-input.spec.ts +50 -50
- package/tests/e2e/select.spec.ts +52 -52
- package/tests/e2e/storybook-utils.ts +59 -59
- package/tests/e2e/test-basic.spec.ts +33 -33
- package/tests/e2e/visual-regression.spec.ts +350 -350
- package/tests/unit/components/Accordion/AccordionGroup.spec.ts.skip +342 -342
- package/tests/unit/components/Accordion/AccordionItem.spec.ts.skip +383 -383
- package/tests/unit/components/Appointment/Card/Actions.spec.ts +407 -407
- package/tests/unit/components/Appointment/Card/Card.spec.ts +485 -485
- package/tests/unit/components/Appointment/Card/Details.spec.ts +397 -397
- package/tests/unit/components/Audio/Audio.spec.ts +403 -403
- package/tests/unit/components/Audio/Waveform.spec.ts +483 -483
- package/tests/unit/components/Core/Button.spec.ts +336 -336
- package/tests/unit/components/Core/Checkbox.spec.ts +544 -544
- package/tests/unit/components/Core/DateInput.spec.ts +690 -690
- package/tests/unit/components/Core/Dialog.spec.ts +485 -485
- package/tests/unit/components/Core/EditField.spec.ts +782 -782
- package/tests/unit/components/Core/Input.spec.ts +512 -512
- package/tests/unit/components/Core/Modal.spec.ts +518 -518
- package/tests/unit/components/Core/NotificationBubble.spec.ts +606 -606
- package/tests/unit/components/Core/OtpInput.spec.ts +708 -708
- package/tests/unit/components/Core/PhoneInput.spec.ts +619 -619
- package/tests/unit/components/Core/Select.spec.ts +712 -712
- package/tests/unit/components/Core/TextArea.spec.ts +565 -565
- package/tests/unit/components/Core/TickBox.spec.ts +779 -779
- package/tests/unit/components/IconBullet/IconBullet.spec.ts +356 -356
- package/tests/unit/components/IconBullet/IconBulletList.spec.ts +371 -371
- package/tests/unit/components/Icons/Audio/CloudFailed.spec.ts +108 -108
- package/tests/unit/components/Icons/Audio/CloudSaved.spec.ts +149 -149
- package/tests/unit/components/Icons/Audio/Delete.spec.ts +158 -158
- package/tests/unit/components/Icons/Audio/Pause.spec.ts +208 -208
- package/tests/unit/components/Icons/Audio/Play.spec.ts +217 -217
- package/tests/unit/components/Icons/CalendarNotification.spec.ts +186 -186
- package/tests/unit/components/Icons/Chair.spec.ts +234 -234
- package/tests/unit/components/Icons/ChairNotification.spec.ts +311 -311
- package/tests/unit/components/Icons/Circle.spec.ts +255 -255
- package/tests/unit/components/Icons/FavIcon.spec.ts +251 -251
- package/tests/unit/components/Icons/FilledCircle.spec.ts +274 -274
- package/tests/unit/components/Icons/Group3.spec.ts +355 -355
- package/tests/unit/components/Icons/RingNotification.spec.ts +393 -393
- package/tests/unit/components/Icons/calendar.spec.ts +286 -286
- package/tests/unit/components/Icons/checkbox.spec.ts +315 -315
- package/tests/unit/components/Icons/outlineChecked.spec.ts +434 -434
- package/tests/unit/components/Icons/play.spec.ts +308 -308
- package/tests/unit/components/Laboratory/AppointmentCard.spec.ts +167 -167
- package/tests/unit/components/Laboratory/ChatBoxImage.spec.ts +179 -179
- package/tests/unit/components/Laboratory/ChatMessage.spec.ts +263 -263
- package/tests/unit/components/Laboratory/ChatMessageBadge.spec.ts +282 -282
- package/tests/unit/components/Laboratory/ChatNotification.spec.ts +256 -256
- package/tests/unit/components/Laboratory/DocumentCard.spec.ts +228 -228
- package/tests/unit/components/Laboratory/DocumentCardItem.spec.ts +236 -236
- package/tests/unit/components/Laboratory/InfoCard.spec.ts +308 -308
- package/tests/unit/components/Laboratory/MainColumnsBar.spec.ts +251 -251
- package/tests/unit/components/Laboratory/ProgressCircle.spec.ts +290 -290
- package/tests/unit/components/Laboratory/ProgressLinear.spec.ts +275 -275
- package/tests/unit/components/Laboratory/SelectionColumnBar.spec.ts +288 -288
- package/tests/unit/components/Laboratory/StatusNotification.spec.ts +296 -296
- package/tests/unit/components/Laboratory/TagLabel.spec.ts +353 -353
- package/tests/unit/components/Laboratory/TagLabelGroup.spec.ts +377 -377
- package/tests/unit/components/Laboratory/TicketCard.spec.ts +351 -351
- package/tests/unit/components/Laboratory/TimeLineEvent.spec.ts +381 -381
- package/tests/unit/components/Laboratory/Timeline.spec.ts +419 -419
- package/tests/unit/constants/iconEnums.spec.ts +39 -39
- package/tests/unit/i18n/i18n.spec.ts +88 -88
- package/tests/unit/plugins/vuetify.spec.ts +220 -220
- package/tests/unit/setup.ts +189 -189
- package/tests/unit/src/components/index.spec.ts.skip +192 -192
- package/tests/unit/src/index.spec.ts.skip +182 -182
- package/tests/unit/src/main.spec.ts +151 -151
- package/tsconfig.json +26 -26
- package/vite.config.ts +29 -29
- package/vitest.config.ts +83 -83
|
@@ -1,404 +1,404 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { mount, VueWrapper } from '@vue/test-utils';
|
|
3
|
-
import Audio from '@components/Audio/Audio.vue';
|
|
4
|
-
|
|
5
|
-
// Mock all icon components
|
|
6
|
-
vi.mock('@components/Icons/Audio/Play.vue', () => ({
|
|
7
|
-
default: {
|
|
8
|
-
name: 'IconPlayAudio',
|
|
9
|
-
template: '<div class="icon-play-audio">Play Icon</div>'
|
|
10
|
-
}
|
|
11
|
-
}));
|
|
12
|
-
|
|
13
|
-
vi.mock('@components/Icons/Audio/Pause.vue', () => ({
|
|
14
|
-
default: {
|
|
15
|
-
name: 'IconPauseAudio',
|
|
16
|
-
template: '<div class="icon-pause-audio">Pause Icon</div>'
|
|
17
|
-
}
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
vi.mock('@components/Icons/Audio/CloudSaved.vue', () => ({
|
|
21
|
-
default: {
|
|
22
|
-
name: 'IconCloudSaved',
|
|
23
|
-
template: '<div class="icon-cloud-saved">Cloud Saved Icon</div>'
|
|
24
|
-
}
|
|
25
|
-
}));
|
|
26
|
-
|
|
27
|
-
vi.mock('@components/Icons/Audio/CloudFailed.vue', () => ({
|
|
28
|
-
default: {
|
|
29
|
-
name: 'IconCloudFailed',
|
|
30
|
-
template: '<div class="icon-cloud-failed">Cloud Failed Icon</div>'
|
|
31
|
-
}
|
|
32
|
-
}));
|
|
33
|
-
|
|
34
|
-
vi.mock('@components/Icons/Audio/Delete.vue', () => ({
|
|
35
|
-
default: {
|
|
36
|
-
name: 'IconDeleteAudio',
|
|
37
|
-
template: '<div class="icon-delete-audio">Delete Icon</div>'
|
|
38
|
-
}
|
|
39
|
-
}));
|
|
40
|
-
|
|
41
|
-
// Mock Waveform component
|
|
42
|
-
vi.mock('@components/Audio/Waveform.vue', () => ({
|
|
43
|
-
default: {
|
|
44
|
-
name: 'WLWaveform',
|
|
45
|
-
template: '<div class="wl-waveform">Waveform Component</div>',
|
|
46
|
-
props: ['src', 'blob'],
|
|
47
|
-
methods: {
|
|
48
|
-
play: vi.fn(),
|
|
49
|
-
pause: vi.fn(),
|
|
50
|
-
getCurrentTime: vi.fn(() => 0)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}));
|
|
54
|
-
|
|
55
|
-
describe('Audio', () => {
|
|
56
|
-
let wrapper: VueWrapper;
|
|
57
|
-
|
|
58
|
-
const defaultProps = {
|
|
59
|
-
id: 1,
|
|
60
|
-
src: 'test-audio.mp3',
|
|
61
|
-
cloudStatus: true,
|
|
62
|
-
viewType: 'OVERVIEW',
|
|
63
|
-
showDeleteButton: true,
|
|
64
|
-
showCloudStatus: true
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
beforeEach(() => {
|
|
68
|
-
wrapper = mount(Audio, {
|
|
69
|
-
props: defaultProps
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe('Rendering', () => {
|
|
74
|
-
it('renders audio container with correct testid', () => {
|
|
75
|
-
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
76
|
-
expect(wrapper.find('.audio-container').exists()).toBe(true);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('renders with waveform component by default', () => {
|
|
80
|
-
expect(wrapper.find('.waveform-container').exists()).toBe(true);
|
|
81
|
-
expect(wrapper.findComponent({ name: 'WLWaveform' }).exists()).toBe(true);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('renders playback controls', () => {
|
|
85
|
-
expect(wrapper.find('.playback-button').exists()).toBe(true);
|
|
86
|
-
expect(wrapper.find('.time').exists()).toBe(true);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('shows play icon when not playing', () => {
|
|
90
|
-
expect(wrapper.findComponent({ name: 'IconPlayAudio' }).exists()).toBe(true);
|
|
91
|
-
expect(wrapper.findComponent({ name: 'IconPauseAudio' }).exists()).toBe(false);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('shows pause icon when playing', async () => {
|
|
95
|
-
// Access the reactive reference directly
|
|
96
|
-
wrapper.vm.isPlaying = true;
|
|
97
|
-
await wrapper.vm.$nextTick();
|
|
98
|
-
|
|
99
|
-
expect(wrapper.findComponent({ name: 'IconPauseAudio' }).exists()).toBe(true);
|
|
100
|
-
expect(wrapper.findComponent({ name: 'IconPlayAudio' }).exists()).toBe(false);
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
describe('Props', () => {
|
|
105
|
-
it('accepts required id prop', () => {
|
|
106
|
-
expect(wrapper.props('id')).toBe(1);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('accepts src prop', () => {
|
|
110
|
-
expect(wrapper.props('src')).toBe('test-audio.mp3');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('accepts blob prop', () => {
|
|
114
|
-
const blob = new Blob(['test'], { type: 'audio/wav' });
|
|
115
|
-
const wrapperWithBlob = mount(Audio, {
|
|
116
|
-
props: { ...defaultProps, blob }
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
expect(wrapperWithBlob.props('blob')).toStrictEqual(blob);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('has correct default values', () => {
|
|
123
|
-
const minimalWrapper = mount(Audio, {
|
|
124
|
-
props: { id: 1 }
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
expect(minimalWrapper.props('cloudStatus')).toBe(false);
|
|
128
|
-
expect(minimalWrapper.props('viewType')).toBe('OVERVIEW');
|
|
129
|
-
expect(minimalWrapper.props('showDeleteButton')).toBe(true);
|
|
130
|
-
expect(minimalWrapper.props('showCloudStatus')).toBe(true);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('accepts viewType prop with enum values', () => {
|
|
134
|
-
const recorderWrapper = mount(Audio, {
|
|
135
|
-
props: { ...defaultProps, viewType: 'RECORDER' }
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
expect(recorderWrapper.props('viewType')).toBe('RECORDER');
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
describe('Cloud Status Display', () => {
|
|
143
|
-
it('shows cloud saved icon when cloudStatus is true', () => {
|
|
144
|
-
expect(wrapper.findComponent({ name: 'IconCloudSaved' }).exists()).toBe(true);
|
|
145
|
-
expect(wrapper.findComponent({ name: 'IconCloudFailed' }).exists()).toBe(false);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('shows cloud failed icon when cloudStatus is false', async () => {
|
|
149
|
-
await wrapper.setProps({ cloudStatus: false });
|
|
150
|
-
|
|
151
|
-
expect(wrapper.findComponent({ name: 'IconCloudFailed' }).exists()).toBe(true);
|
|
152
|
-
expect(wrapper.findComponent({ name: 'IconCloudSaved' }).exists()).toBe(false);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('hides cloud status when showCloudStatus is false', async () => {
|
|
156
|
-
await wrapper.setProps({ showCloudStatus: false });
|
|
157
|
-
|
|
158
|
-
expect(wrapper.find('.cloud-status').exists()).toBe(false);
|
|
159
|
-
expect(wrapper.findComponent({ name: 'IconCloudSaved' }).exists()).toBe(false);
|
|
160
|
-
expect(wrapper.findComponent({ name: 'IconCloudFailed' }).exists()).toBe(false);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('shows cloud status when showCloudStatus is true', () => {
|
|
164
|
-
expect(wrapper.find('.cloud-status').exists()).toBe(true);
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
describe('Delete Button', () => {
|
|
169
|
-
it('shows delete button when showDeleteButton is true', () => {
|
|
170
|
-
expect(wrapper.find('.delete-button').exists()).toBe(true);
|
|
171
|
-
expect(wrapper.findComponent({ name: 'IconDeleteAudio' }).exists()).toBe(true);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it('hides delete button when showDeleteButton is false', async () => {
|
|
175
|
-
await wrapper.setProps({ showDeleteButton: false });
|
|
176
|
-
|
|
177
|
-
expect(wrapper.find('.delete-button').exists()).toBe(false);
|
|
178
|
-
expect(wrapper.findComponent({ name: 'IconDeleteAudio' }).exists()).toBe(false);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it('emits remove event with id when delete button is clicked', async () => {
|
|
182
|
-
const deleteIcon = wrapper.findComponent({ name: 'IconDeleteAudio' });
|
|
183
|
-
await deleteIcon.trigger('click');
|
|
184
|
-
|
|
185
|
-
expect(wrapper.emitted('remove')).toBeTruthy();
|
|
186
|
-
expect(wrapper.emitted('remove')[0]).toEqual([1]);
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
describe('Time Display', () => {
|
|
191
|
-
it('displays current time', () => {
|
|
192
|
-
const timeElement = wrapper.find('.time');
|
|
193
|
-
expect(timeElement.exists()).toBe(true);
|
|
194
|
-
expect(timeElement.text()).toBe('00:00');
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('updates time display when updateTime is called', async () => {
|
|
198
|
-
await wrapper.vm.updateTime(125); // 2:05
|
|
199
|
-
|
|
200
|
-
expect(wrapper.find('.time').text()).toBe('02:05');
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it('formats time correctly', () => {
|
|
204
|
-
// Test the formatTime function indirectly
|
|
205
|
-
expect(wrapper.vm.formatTime(0)).toBe('00:00');
|
|
206
|
-
expect(wrapper.vm.formatTime(65)).toBe('01:05');
|
|
207
|
-
expect(wrapper.vm.formatTime(3661)).toBe('61:01');
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
describe('Playback Controls', () => {
|
|
212
|
-
it('toggles play state when play button is clicked', async () => {
|
|
213
|
-
const playButton = wrapper.findComponent({ name: 'IconPlayAudio' });
|
|
214
|
-
|
|
215
|
-
// Mock the audioFile ref methods
|
|
216
|
-
const mockAudioFile = {
|
|
217
|
-
play: vi.fn(),
|
|
218
|
-
pause: vi.fn()
|
|
219
|
-
};
|
|
220
|
-
wrapper.vm.audioFile = mockAudioFile;
|
|
221
|
-
|
|
222
|
-
await playButton.trigger('click');
|
|
223
|
-
|
|
224
|
-
expect(mockAudioFile.play).toHaveBeenCalled();
|
|
225
|
-
expect(wrapper.vm.isPlaying).toBe(true);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it('toggles pause state when pause button is clicked', async () => {
|
|
229
|
-
wrapper.vm.isPlaying = true;
|
|
230
|
-
await wrapper.vm.$nextTick();
|
|
231
|
-
|
|
232
|
-
const mockAudioFile = {
|
|
233
|
-
play: vi.fn(),
|
|
234
|
-
pause: vi.fn()
|
|
235
|
-
};
|
|
236
|
-
wrapper.vm.audioFile = mockAudioFile;
|
|
237
|
-
|
|
238
|
-
const pauseButton = wrapper.findComponent({ name: 'IconPauseAudio' });
|
|
239
|
-
await pauseButton.trigger('click');
|
|
240
|
-
|
|
241
|
-
expect(mockAudioFile.pause).toHaveBeenCalled();
|
|
242
|
-
expect(wrapper.vm.isPlaying).toBe(false);
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it('handles togglePlay when audioFile is not available', async () => {
|
|
246
|
-
wrapper.vm.audioFile = null;
|
|
247
|
-
|
|
248
|
-
expect(() => {
|
|
249
|
-
wrapper.vm.togglePlay();
|
|
250
|
-
}).not.toThrow();
|
|
251
|
-
|
|
252
|
-
expect(wrapper.vm.isPlaying).toBe(false);
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
describe('Waveform Integration', () => {
|
|
257
|
-
it('passes correct props to WLWaveform component', () => {
|
|
258
|
-
const waveformComponent = wrapper.findComponent({ name: 'WLWaveform' });
|
|
259
|
-
|
|
260
|
-
expect(waveformComponent.props('src')).toBe('test-audio.mp3');
|
|
261
|
-
expect(waveformComponent.props('blob')).toBeUndefined();
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
it('passes blob prop to WLWaveform when provided', async () => {
|
|
265
|
-
const blob = new Blob(['test'], { type: 'audio/wav' });
|
|
266
|
-
await wrapper.setProps({ blob });
|
|
267
|
-
|
|
268
|
-
const waveformComponent = wrapper.findComponent({ name: 'WLWaveform' });
|
|
269
|
-
expect(waveformComponent.props('blob')).toStrictEqual(blob);
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
it('handles waveform timeupdate event', async () => {
|
|
273
|
-
const waveformComponent = wrapper.findComponent({ name: 'WLWaveform' });
|
|
274
|
-
|
|
275
|
-
await waveformComponent.vm.$emit('timeupdate', 30);
|
|
276
|
-
|
|
277
|
-
expect(wrapper.find('.time').text()).toBe('00:30');
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
it('handles waveform finish event', async () => {
|
|
281
|
-
wrapper.vm.isPlaying = true;
|
|
282
|
-
await wrapper.vm.$nextTick();
|
|
283
|
-
|
|
284
|
-
const waveformComponent = wrapper.findComponent({ name: 'WLWaveform' });
|
|
285
|
-
await waveformComponent.vm.$emit('finish');
|
|
286
|
-
|
|
287
|
-
expect(wrapper.vm.isPlaying).toBe(false);
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it('handles waveform ready event', async () => {
|
|
291
|
-
const waveformComponent = wrapper.findComponent({ name: 'WLWaveform' });
|
|
292
|
-
|
|
293
|
-
await waveformComponent.vm.$emit('ready', 180); // 3 minutes
|
|
294
|
-
|
|
295
|
-
expect(wrapper.vm.totalDuration).toBe('03:00');
|
|
296
|
-
expect(wrapper.find('.time').text()).toBe('03:00');
|
|
297
|
-
});
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
describe('Fallback Audio Element', () => {
|
|
301
|
-
it('shows native audio element when useWaveformComponent is false', async () => {
|
|
302
|
-
wrapper.vm.useWaveformComponent = false;
|
|
303
|
-
await wrapper.vm.$nextTick();
|
|
304
|
-
|
|
305
|
-
expect(wrapper.find('audio').exists()).toBe(true);
|
|
306
|
-
expect(wrapper.find('.waveform-container').exists()).toBe(false);
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
it('native audio element has correct attributes', async () => {
|
|
310
|
-
wrapper.vm.useWaveformComponent = false;
|
|
311
|
-
await wrapper.vm.$nextTick();
|
|
312
|
-
|
|
313
|
-
const audioElement = wrapper.find('audio');
|
|
314
|
-
expect(audioElement.attributes('src')).toBe('test-audio.mp3');
|
|
315
|
-
expect(audioElement.attributes('controls')).toBeDefined();
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
describe('Event Handling', () => {
|
|
320
|
-
it('deleteAudio function emits remove event', () => {
|
|
321
|
-
wrapper.vm.deleteAudio();
|
|
322
|
-
|
|
323
|
-
expect(wrapper.emitted('remove')).toBeTruthy();
|
|
324
|
-
expect(wrapper.emitted('remove')[0]).toEqual([1]);
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
it('onFinish sets isPlaying to false', () => {
|
|
328
|
-
wrapper.vm.isPlaying = true;
|
|
329
|
-
wrapper.vm.onFinish();
|
|
330
|
-
|
|
331
|
-
expect(wrapper.vm.isPlaying).toBe(false);
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
it('onLoaded sets duration and current time', () => {
|
|
335
|
-
wrapper.vm.onLoaded(240); // 4 minutes
|
|
336
|
-
|
|
337
|
-
expect(wrapper.vm.totalDuration).toBe('04:00');
|
|
338
|
-
expect(wrapper.vm.currentTimeText).toBe('04:00');
|
|
339
|
-
});
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
describe('Component Integration', () => {
|
|
343
|
-
it('can be mounted with minimal props', () => {
|
|
344
|
-
expect(() => {
|
|
345
|
-
mount(Audio, {
|
|
346
|
-
props: { id: 1 }
|
|
347
|
-
});
|
|
348
|
-
}).not.toThrow();
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it('renders consistently on multiple mounts', () => {
|
|
352
|
-
const wrapper1 = mount(Audio, {
|
|
353
|
-
props: defaultProps
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
const wrapper2 = mount(Audio, {
|
|
357
|
-
props: defaultProps
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
expect(wrapper1.find('.audio-container').exists()).toBe(true);
|
|
361
|
-
expect(wrapper2.find('.audio-container').exists()).toBe(true);
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
describe('Error Handling', () => {
|
|
366
|
-
it('handles missing src and blob props gracefully', () => {
|
|
367
|
-
const wrapperWithoutSrc = mount(Audio, {
|
|
368
|
-
props: { id: 2 }
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
expect(wrapperWithoutSrc.find('.audio-container').exists()).toBe(true);
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
it('handles invalid id prop type', () => {
|
|
375
|
-
const wrapperWithStringId = mount(Audio, {
|
|
376
|
-
props: { ...defaultProps, id: '123' as any }
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
expect(wrapperWithStringId.props('id')).toBe('123');
|
|
380
|
-
});
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
describe('Styling Classes', () => {
|
|
384
|
-
it('applies correct CSS classes', () => {
|
|
385
|
-
expect(wrapper.find('.audio-container').exists()).toBe(true);
|
|
386
|
-
expect(wrapper.find('.audio-controls').exists()).toBe(true);
|
|
387
|
-
expect(wrapper.find('.playback-button').exists()).toBe(true);
|
|
388
|
-
expect(wrapper.find('.time').exists()).toBe(true);
|
|
389
|
-
expect(wrapper.find('.cloud-status').exists()).toBe(true);
|
|
390
|
-
expect(wrapper.find('.delete-button').exists()).toBe(true);
|
|
391
|
-
});
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
describe('Accessibility', () => {
|
|
395
|
-
it('provides testid for testing', () => {
|
|
396
|
-
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
it('maintains semantic structure', () => {
|
|
400
|
-
expect(wrapper.find('.audio-container').exists()).toBe(true);
|
|
401
|
-
expect(wrapper.find('.audio-controls').exists()).toBe(true);
|
|
402
|
-
});
|
|
403
|
-
});
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { mount, VueWrapper } from '@vue/test-utils';
|
|
3
|
+
import Audio from '@components/Audio/Audio.vue';
|
|
4
|
+
|
|
5
|
+
// Mock all icon components
|
|
6
|
+
vi.mock('@components/Icons/Audio/Play.vue', () => ({
|
|
7
|
+
default: {
|
|
8
|
+
name: 'IconPlayAudio',
|
|
9
|
+
template: '<div class="icon-play-audio">Play Icon</div>'
|
|
10
|
+
}
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock('@components/Icons/Audio/Pause.vue', () => ({
|
|
14
|
+
default: {
|
|
15
|
+
name: 'IconPauseAudio',
|
|
16
|
+
template: '<div class="icon-pause-audio">Pause Icon</div>'
|
|
17
|
+
}
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
vi.mock('@components/Icons/Audio/CloudSaved.vue', () => ({
|
|
21
|
+
default: {
|
|
22
|
+
name: 'IconCloudSaved',
|
|
23
|
+
template: '<div class="icon-cloud-saved">Cloud Saved Icon</div>'
|
|
24
|
+
}
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
vi.mock('@components/Icons/Audio/CloudFailed.vue', () => ({
|
|
28
|
+
default: {
|
|
29
|
+
name: 'IconCloudFailed',
|
|
30
|
+
template: '<div class="icon-cloud-failed">Cloud Failed Icon</div>'
|
|
31
|
+
}
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
vi.mock('@components/Icons/Audio/Delete.vue', () => ({
|
|
35
|
+
default: {
|
|
36
|
+
name: 'IconDeleteAudio',
|
|
37
|
+
template: '<div class="icon-delete-audio">Delete Icon</div>'
|
|
38
|
+
}
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
// Mock Waveform component
|
|
42
|
+
vi.mock('@components/Audio/Waveform.vue', () => ({
|
|
43
|
+
default: {
|
|
44
|
+
name: 'WLWaveform',
|
|
45
|
+
template: '<div class="wl-waveform">Waveform Component</div>',
|
|
46
|
+
props: ['src', 'blob'],
|
|
47
|
+
methods: {
|
|
48
|
+
play: vi.fn(),
|
|
49
|
+
pause: vi.fn(),
|
|
50
|
+
getCurrentTime: vi.fn(() => 0)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
describe('Audio', () => {
|
|
56
|
+
let wrapper: VueWrapper;
|
|
57
|
+
|
|
58
|
+
const defaultProps = {
|
|
59
|
+
id: 1,
|
|
60
|
+
src: 'test-audio.mp3',
|
|
61
|
+
cloudStatus: true,
|
|
62
|
+
viewType: 'OVERVIEW',
|
|
63
|
+
showDeleteButton: true,
|
|
64
|
+
showCloudStatus: true
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
beforeEach(() => {
|
|
68
|
+
wrapper = mount(Audio, {
|
|
69
|
+
props: defaultProps
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('Rendering', () => {
|
|
74
|
+
it('renders audio container with correct testid', () => {
|
|
75
|
+
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
76
|
+
expect(wrapper.find('.audio-container').exists()).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('renders with waveform component by default', () => {
|
|
80
|
+
expect(wrapper.find('.waveform-container').exists()).toBe(true);
|
|
81
|
+
expect(wrapper.findComponent({ name: 'WLWaveform' }).exists()).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('renders playback controls', () => {
|
|
85
|
+
expect(wrapper.find('.playback-button').exists()).toBe(true);
|
|
86
|
+
expect(wrapper.find('.time').exists()).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('shows play icon when not playing', () => {
|
|
90
|
+
expect(wrapper.findComponent({ name: 'IconPlayAudio' }).exists()).toBe(true);
|
|
91
|
+
expect(wrapper.findComponent({ name: 'IconPauseAudio' }).exists()).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('shows pause icon when playing', async () => {
|
|
95
|
+
// Access the reactive reference directly
|
|
96
|
+
wrapper.vm.isPlaying = true;
|
|
97
|
+
await wrapper.vm.$nextTick();
|
|
98
|
+
|
|
99
|
+
expect(wrapper.findComponent({ name: 'IconPauseAudio' }).exists()).toBe(true);
|
|
100
|
+
expect(wrapper.findComponent({ name: 'IconPlayAudio' }).exists()).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('Props', () => {
|
|
105
|
+
it('accepts required id prop', () => {
|
|
106
|
+
expect(wrapper.props('id')).toBe(1);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('accepts src prop', () => {
|
|
110
|
+
expect(wrapper.props('src')).toBe('test-audio.mp3');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('accepts blob prop', () => {
|
|
114
|
+
const blob = new Blob(['test'], { type: 'audio/wav' });
|
|
115
|
+
const wrapperWithBlob = mount(Audio, {
|
|
116
|
+
props: { ...defaultProps, blob }
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(wrapperWithBlob.props('blob')).toStrictEqual(blob);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('has correct default values', () => {
|
|
123
|
+
const minimalWrapper = mount(Audio, {
|
|
124
|
+
props: { id: 1 }
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(minimalWrapper.props('cloudStatus')).toBe(false);
|
|
128
|
+
expect(minimalWrapper.props('viewType')).toBe('OVERVIEW');
|
|
129
|
+
expect(minimalWrapper.props('showDeleteButton')).toBe(true);
|
|
130
|
+
expect(minimalWrapper.props('showCloudStatus')).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('accepts viewType prop with enum values', () => {
|
|
134
|
+
const recorderWrapper = mount(Audio, {
|
|
135
|
+
props: { ...defaultProps, viewType: 'RECORDER' }
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(recorderWrapper.props('viewType')).toBe('RECORDER');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('Cloud Status Display', () => {
|
|
143
|
+
it('shows cloud saved icon when cloudStatus is true', () => {
|
|
144
|
+
expect(wrapper.findComponent({ name: 'IconCloudSaved' }).exists()).toBe(true);
|
|
145
|
+
expect(wrapper.findComponent({ name: 'IconCloudFailed' }).exists()).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('shows cloud failed icon when cloudStatus is false', async () => {
|
|
149
|
+
await wrapper.setProps({ cloudStatus: false });
|
|
150
|
+
|
|
151
|
+
expect(wrapper.findComponent({ name: 'IconCloudFailed' }).exists()).toBe(true);
|
|
152
|
+
expect(wrapper.findComponent({ name: 'IconCloudSaved' }).exists()).toBe(false);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('hides cloud status when showCloudStatus is false', async () => {
|
|
156
|
+
await wrapper.setProps({ showCloudStatus: false });
|
|
157
|
+
|
|
158
|
+
expect(wrapper.find('.cloud-status').exists()).toBe(false);
|
|
159
|
+
expect(wrapper.findComponent({ name: 'IconCloudSaved' }).exists()).toBe(false);
|
|
160
|
+
expect(wrapper.findComponent({ name: 'IconCloudFailed' }).exists()).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('shows cloud status when showCloudStatus is true', () => {
|
|
164
|
+
expect(wrapper.find('.cloud-status').exists()).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('Delete Button', () => {
|
|
169
|
+
it('shows delete button when showDeleteButton is true', () => {
|
|
170
|
+
expect(wrapper.find('.delete-button').exists()).toBe(true);
|
|
171
|
+
expect(wrapper.findComponent({ name: 'IconDeleteAudio' }).exists()).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('hides delete button when showDeleteButton is false', async () => {
|
|
175
|
+
await wrapper.setProps({ showDeleteButton: false });
|
|
176
|
+
|
|
177
|
+
expect(wrapper.find('.delete-button').exists()).toBe(false);
|
|
178
|
+
expect(wrapper.findComponent({ name: 'IconDeleteAudio' }).exists()).toBe(false);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('emits remove event with id when delete button is clicked', async () => {
|
|
182
|
+
const deleteIcon = wrapper.findComponent({ name: 'IconDeleteAudio' });
|
|
183
|
+
await deleteIcon.trigger('click');
|
|
184
|
+
|
|
185
|
+
expect(wrapper.emitted('remove')).toBeTruthy();
|
|
186
|
+
expect(wrapper.emitted('remove')[0]).toEqual([1]);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('Time Display', () => {
|
|
191
|
+
it('displays current time', () => {
|
|
192
|
+
const timeElement = wrapper.find('.time');
|
|
193
|
+
expect(timeElement.exists()).toBe(true);
|
|
194
|
+
expect(timeElement.text()).toBe('00:00');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('updates time display when updateTime is called', async () => {
|
|
198
|
+
await wrapper.vm.updateTime(125); // 2:05
|
|
199
|
+
|
|
200
|
+
expect(wrapper.find('.time').text()).toBe('02:05');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('formats time correctly', () => {
|
|
204
|
+
// Test the formatTime function indirectly
|
|
205
|
+
expect(wrapper.vm.formatTime(0)).toBe('00:00');
|
|
206
|
+
expect(wrapper.vm.formatTime(65)).toBe('01:05');
|
|
207
|
+
expect(wrapper.vm.formatTime(3661)).toBe('61:01');
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('Playback Controls', () => {
|
|
212
|
+
it('toggles play state when play button is clicked', async () => {
|
|
213
|
+
const playButton = wrapper.findComponent({ name: 'IconPlayAudio' });
|
|
214
|
+
|
|
215
|
+
// Mock the audioFile ref methods
|
|
216
|
+
const mockAudioFile = {
|
|
217
|
+
play: vi.fn(),
|
|
218
|
+
pause: vi.fn()
|
|
219
|
+
};
|
|
220
|
+
wrapper.vm.audioFile = mockAudioFile;
|
|
221
|
+
|
|
222
|
+
await playButton.trigger('click');
|
|
223
|
+
|
|
224
|
+
expect(mockAudioFile.play).toHaveBeenCalled();
|
|
225
|
+
expect(wrapper.vm.isPlaying).toBe(true);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('toggles pause state when pause button is clicked', async () => {
|
|
229
|
+
wrapper.vm.isPlaying = true;
|
|
230
|
+
await wrapper.vm.$nextTick();
|
|
231
|
+
|
|
232
|
+
const mockAudioFile = {
|
|
233
|
+
play: vi.fn(),
|
|
234
|
+
pause: vi.fn()
|
|
235
|
+
};
|
|
236
|
+
wrapper.vm.audioFile = mockAudioFile;
|
|
237
|
+
|
|
238
|
+
const pauseButton = wrapper.findComponent({ name: 'IconPauseAudio' });
|
|
239
|
+
await pauseButton.trigger('click');
|
|
240
|
+
|
|
241
|
+
expect(mockAudioFile.pause).toHaveBeenCalled();
|
|
242
|
+
expect(wrapper.vm.isPlaying).toBe(false);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('handles togglePlay when audioFile is not available', async () => {
|
|
246
|
+
wrapper.vm.audioFile = null;
|
|
247
|
+
|
|
248
|
+
expect(() => {
|
|
249
|
+
wrapper.vm.togglePlay();
|
|
250
|
+
}).not.toThrow();
|
|
251
|
+
|
|
252
|
+
expect(wrapper.vm.isPlaying).toBe(false);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe('Waveform Integration', () => {
|
|
257
|
+
it('passes correct props to WLWaveform component', () => {
|
|
258
|
+
const waveformComponent = wrapper.findComponent({ name: 'WLWaveform' });
|
|
259
|
+
|
|
260
|
+
expect(waveformComponent.props('src')).toBe('test-audio.mp3');
|
|
261
|
+
expect(waveformComponent.props('blob')).toBeUndefined();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('passes blob prop to WLWaveform when provided', async () => {
|
|
265
|
+
const blob = new Blob(['test'], { type: 'audio/wav' });
|
|
266
|
+
await wrapper.setProps({ blob });
|
|
267
|
+
|
|
268
|
+
const waveformComponent = wrapper.findComponent({ name: 'WLWaveform' });
|
|
269
|
+
expect(waveformComponent.props('blob')).toStrictEqual(blob);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('handles waveform timeupdate event', async () => {
|
|
273
|
+
const waveformComponent = wrapper.findComponent({ name: 'WLWaveform' });
|
|
274
|
+
|
|
275
|
+
await waveformComponent.vm.$emit('timeupdate', 30);
|
|
276
|
+
|
|
277
|
+
expect(wrapper.find('.time').text()).toBe('00:30');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('handles waveform finish event', async () => {
|
|
281
|
+
wrapper.vm.isPlaying = true;
|
|
282
|
+
await wrapper.vm.$nextTick();
|
|
283
|
+
|
|
284
|
+
const waveformComponent = wrapper.findComponent({ name: 'WLWaveform' });
|
|
285
|
+
await waveformComponent.vm.$emit('finish');
|
|
286
|
+
|
|
287
|
+
expect(wrapper.vm.isPlaying).toBe(false);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('handles waveform ready event', async () => {
|
|
291
|
+
const waveformComponent = wrapper.findComponent({ name: 'WLWaveform' });
|
|
292
|
+
|
|
293
|
+
await waveformComponent.vm.$emit('ready', 180); // 3 minutes
|
|
294
|
+
|
|
295
|
+
expect(wrapper.vm.totalDuration).toBe('03:00');
|
|
296
|
+
expect(wrapper.find('.time').text()).toBe('03:00');
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
describe('Fallback Audio Element', () => {
|
|
301
|
+
it('shows native audio element when useWaveformComponent is false', async () => {
|
|
302
|
+
wrapper.vm.useWaveformComponent = false;
|
|
303
|
+
await wrapper.vm.$nextTick();
|
|
304
|
+
|
|
305
|
+
expect(wrapper.find('audio').exists()).toBe(true);
|
|
306
|
+
expect(wrapper.find('.waveform-container').exists()).toBe(false);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('native audio element has correct attributes', async () => {
|
|
310
|
+
wrapper.vm.useWaveformComponent = false;
|
|
311
|
+
await wrapper.vm.$nextTick();
|
|
312
|
+
|
|
313
|
+
const audioElement = wrapper.find('audio');
|
|
314
|
+
expect(audioElement.attributes('src')).toBe('test-audio.mp3');
|
|
315
|
+
expect(audioElement.attributes('controls')).toBeDefined();
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
describe('Event Handling', () => {
|
|
320
|
+
it('deleteAudio function emits remove event', () => {
|
|
321
|
+
wrapper.vm.deleteAudio();
|
|
322
|
+
|
|
323
|
+
expect(wrapper.emitted('remove')).toBeTruthy();
|
|
324
|
+
expect(wrapper.emitted('remove')[0]).toEqual([1]);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('onFinish sets isPlaying to false', () => {
|
|
328
|
+
wrapper.vm.isPlaying = true;
|
|
329
|
+
wrapper.vm.onFinish();
|
|
330
|
+
|
|
331
|
+
expect(wrapper.vm.isPlaying).toBe(false);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('onLoaded sets duration and current time', () => {
|
|
335
|
+
wrapper.vm.onLoaded(240); // 4 minutes
|
|
336
|
+
|
|
337
|
+
expect(wrapper.vm.totalDuration).toBe('04:00');
|
|
338
|
+
expect(wrapper.vm.currentTimeText).toBe('04:00');
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
describe('Component Integration', () => {
|
|
343
|
+
it('can be mounted with minimal props', () => {
|
|
344
|
+
expect(() => {
|
|
345
|
+
mount(Audio, {
|
|
346
|
+
props: { id: 1 }
|
|
347
|
+
});
|
|
348
|
+
}).not.toThrow();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('renders consistently on multiple mounts', () => {
|
|
352
|
+
const wrapper1 = mount(Audio, {
|
|
353
|
+
props: defaultProps
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const wrapper2 = mount(Audio, {
|
|
357
|
+
props: defaultProps
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
expect(wrapper1.find('.audio-container').exists()).toBe(true);
|
|
361
|
+
expect(wrapper2.find('.audio-container').exists()).toBe(true);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
describe('Error Handling', () => {
|
|
366
|
+
it('handles missing src and blob props gracefully', () => {
|
|
367
|
+
const wrapperWithoutSrc = mount(Audio, {
|
|
368
|
+
props: { id: 2 }
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
expect(wrapperWithoutSrc.find('.audio-container').exists()).toBe(true);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('handles invalid id prop type', () => {
|
|
375
|
+
const wrapperWithStringId = mount(Audio, {
|
|
376
|
+
props: { ...defaultProps, id: '123' as any }
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
expect(wrapperWithStringId.props('id')).toBe('123');
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
describe('Styling Classes', () => {
|
|
384
|
+
it('applies correct CSS classes', () => {
|
|
385
|
+
expect(wrapper.find('.audio-container').exists()).toBe(true);
|
|
386
|
+
expect(wrapper.find('.audio-controls').exists()).toBe(true);
|
|
387
|
+
expect(wrapper.find('.playback-button').exists()).toBe(true);
|
|
388
|
+
expect(wrapper.find('.time').exists()).toBe(true);
|
|
389
|
+
expect(wrapper.find('.cloud-status').exists()).toBe(true);
|
|
390
|
+
expect(wrapper.find('.delete-button').exists()).toBe(true);
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
describe('Accessibility', () => {
|
|
395
|
+
it('provides testid for testing', () => {
|
|
396
|
+
expect(wrapper.find('[data-testid="root"]').exists()).toBe(true);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('maintains semantic structure', () => {
|
|
400
|
+
expect(wrapper.find('.audio-container').exists()).toBe(true);
|
|
401
|
+
expect(wrapper.find('.audio-controls').exists()).toBe(true);
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
404
|
});
|