@zap-wunschlachen/wl-shared-components 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/.github/workflows/playwright.yml +215 -0
  2. package/.github/workflows/static.yml +62 -0
  3. package/.prettierrc +5 -0
  4. package/.storybook/main.ts +18 -0
  5. package/.storybook/preview.ts +37 -0
  6. package/.storybook/storyWrapper.vue +18 -0
  7. package/.storybook/withVuetifyTheme.decorator.ts +21 -0
  8. package/App.vue +95 -0
  9. package/README.md +56 -0
  10. package/heroicons.ts +75 -0
  11. package/index.html +19 -0
  12. package/package.json +66 -0
  13. package/playwright.config.ts +35 -0
  14. package/public/audio/dummy_pink_noise.wav +0 -0
  15. package/public/background.svg +60 -0
  16. package/public/javascript.svg +1 -0
  17. package/public/style.css +187 -0
  18. package/public/technologies.svg +22 -0
  19. package/src/assets/css/base.css +235 -0
  20. package/src/assets/css/variables.css +96 -0
  21. package/src/assets/fonts/Outfit-Black.ttf +0 -0
  22. package/src/assets/fonts/Outfit-Bold.ttf +0 -0
  23. package/src/assets/fonts/Outfit-ExtraBold.ttf +0 -0
  24. package/src/assets/fonts/Outfit-ExtraLight.ttf +0 -0
  25. package/src/assets/fonts/Outfit-Light.ttf +0 -0
  26. package/src/assets/fonts/Outfit-Medium.ttf +0 -0
  27. package/src/assets/fonts/Outfit-Regular.ttf +0 -0
  28. package/src/assets/fonts/Outfit-SemiBold.ttf +0 -0
  29. package/src/assets/fonts/Outfit-Thin.ttf +0 -0
  30. package/src/components/Accordion/Accordion.css +59 -0
  31. package/src/components/Accordion/AccordionGroup.vue +51 -0
  32. package/src/components/Accordion/AccordionItem.vue +66 -0
  33. package/src/components/Appointment/Card/Actions.css +30 -0
  34. package/src/components/Appointment/Card/Actions.vue +66 -0
  35. package/src/components/Appointment/Card/Card.css +49 -0
  36. package/src/components/Appointment/Card/Card.vue +55 -0
  37. package/src/components/Appointment/Card/Details.css +51 -0
  38. package/src/components/Appointment/Card/Details.vue +44 -0
  39. package/src/components/Audio/Audio.vue +188 -0
  40. package/src/components/Audio/Waveform.vue +118 -0
  41. package/src/components/Button/Button.vue +119 -0
  42. package/src/components/CheckBox/CheckBox.css +185 -0
  43. package/src/components/CheckBox/Checkbox.vue +130 -0
  44. package/src/components/DateInput/DateInput.css +3 -0
  45. package/src/components/DateInput/DateInput.vue +263 -0
  46. package/src/components/Dialog/Dialog.css +6 -0
  47. package/src/components/Dialog/Dialog.vue +29 -0
  48. package/src/components/EditField/EditField.css +20 -0
  49. package/src/components/EditField/EditField.vue +202 -0
  50. package/src/components/IconBullet/IconBullet.vue +86 -0
  51. package/src/components/IconBullet/IconBulletList.vue +41 -0
  52. package/src/components/Icons/Audio/CloudFailed.vue +21 -0
  53. package/src/components/Icons/Audio/CloudSaved.vue +22 -0
  54. package/src/components/Icons/Audio/Delete.vue +16 -0
  55. package/src/components/Icons/Audio/Pause.vue +19 -0
  56. package/src/components/Icons/Audio/Play.vue +16 -0
  57. package/src/components/Icons/CalendarNotification.vue +126 -0
  58. package/src/components/Icons/Chair.vue +32 -0
  59. package/src/components/Icons/ChairNotification.vue +35 -0
  60. package/src/components/Icons/Circle.vue +66 -0
  61. package/src/components/Icons/FavIcon.vue +22 -0
  62. package/src/components/Icons/FilledCircle.vue +11 -0
  63. package/src/components/Icons/Group3.vue +46 -0
  64. package/src/components/Icons/RingNotification.vue +54 -0
  65. package/src/components/Icons/SolidArrowRight.vue +14 -0
  66. package/src/components/Icons/calendar.vue +17 -0
  67. package/src/components/Icons/checkbox.vue +19 -0
  68. package/src/components/Icons/outlineChecked.vue +27 -0
  69. package/src/components/Icons/play.vue +6 -0
  70. package/src/components/Input/Input.css +187 -0
  71. package/src/components/Input/Input.vue +247 -0
  72. package/src/components/Laboratory/AppointmentCard/AppointmentCard.css +7 -0
  73. package/src/components/Laboratory/AppointmentCard/AppointmentCard.vue +116 -0
  74. package/src/components/Laboratory/ChatBoxImage/ChatBoxImage.vue +81 -0
  75. package/src/components/Laboratory/ChatMessage/ChatMessage.vue +113 -0
  76. package/src/components/Laboratory/ChatMessage/ChatMessageBadge.css +4 -0
  77. package/src/components/Laboratory/ChatMessage/ChatMessageBadge.vue +99 -0
  78. package/src/components/Laboratory/ChatNotification/ChatNotification.vue +130 -0
  79. package/src/components/Laboratory/DocumentCard/DocumentCard.css +3 -0
  80. package/src/components/Laboratory/DocumentCard/DocumentCard.vue +50 -0
  81. package/src/components/Laboratory/DocumentCard/DocumentCardItem.vue +53 -0
  82. package/src/components/Laboratory/InfoCard/InfoCard.vue +162 -0
  83. package/src/components/Laboratory/MainColumnsBar/MainColumnsBar.vue +102 -0
  84. package/src/components/Laboratory/ProgressCircle/ProgressCircle.vue +152 -0
  85. package/src/components/Laboratory/ProgressLinear/ProgressLinear.css +33 -0
  86. package/src/components/Laboratory/ProgressLinear/ProgressLinear.vue +75 -0
  87. package/src/components/Laboratory/SelectionColumnBar/SelectionColumnBar.vue +92 -0
  88. package/src/components/Laboratory/StatusNotification/StatusNotification.vue +49 -0
  89. package/src/components/Laboratory/TagLabel/TagLabel.vue +126 -0
  90. package/src/components/Laboratory/TagLabelGroup/TagLabelGroup.vue +97 -0
  91. package/src/components/Laboratory/TicketCard/TicketCard.css +3 -0
  92. package/src/components/Laboratory/TicketCard/TicketCard.vue +143 -0
  93. package/src/components/Laboratory/TimeLine/TimeLineEvent.css +18 -0
  94. package/src/components/Laboratory/TimeLine/TimeLineEvent.vue +119 -0
  95. package/src/components/Laboratory/TimeLine/Timeline.css +4 -0
  96. package/src/components/Laboratory/TimeLine/Timeline.vue +30 -0
  97. package/src/components/Modal/Modal.css +6 -0
  98. package/src/components/Modal/Modal.vue +23 -0
  99. package/src/components/NotificationBubble/NotificationBubble.css +4 -0
  100. package/src/components/NotificationBubble/NotificationBubble.vue +90 -0
  101. package/src/components/OtpInput/OtpInput.css +39 -0
  102. package/src/components/OtpInput/OtpInput.vue +144 -0
  103. package/src/components/PhoneInput/PhoneInput.css +32 -0
  104. package/src/components/PhoneInput/PhoneInput.vue +114 -0
  105. package/src/components/Select/Select.css +150 -0
  106. package/src/components/Select/Select.vue +304 -0
  107. package/src/components/TextArea/TextArea.css +3 -0
  108. package/src/components/TextArea/TextArea.vue +126 -0
  109. package/src/components/TickBox/TickBox.css +49 -0
  110. package/src/components/TickBox/TickBox.vue +126 -0
  111. package/src/components/index.ts +20 -0
  112. package/src/constants/buttonEnums.ts +0 -0
  113. package/src/constants/iconEnums.ts +4 -0
  114. package/src/i18n/i18n.ts +16 -0
  115. package/src/i18n/locales/de.json +19 -0
  116. package/src/i18n/locales/en.json +19 -0
  117. package/src/index.ts +31 -0
  118. package/src/main.ts +11 -0
  119. package/src/plugins/vuetify.ts +131 -0
  120. package/src/shims-vue.d.ts +10 -0
  121. package/src/stories/Accordion.stories.ts +650 -0
  122. package/src/stories/Audio.stories.ts +29 -0
  123. package/src/stories/Button.stories.ts +263 -0
  124. package/src/stories/CheckBox.stories.ts +348 -0
  125. package/src/stories/DateInput.stories.ts +54 -0
  126. package/src/stories/Dialog.stories.ts +147 -0
  127. package/src/stories/EditField.stories.ts +79 -0
  128. package/src/stories/IconBullet/IconBullet.stories.ts +201 -0
  129. package/src/stories/IconBullet/IconBulletList.stories.ts +275 -0
  130. package/src/stories/Input.stories.ts +351 -0
  131. package/src/stories/Laboratory/Cards/AppointmentCard/AppointmentCard.stories.ts +260 -0
  132. package/src/stories/Laboratory/Cards/DocumentCard/DocumentCard.stories.ts +176 -0
  133. package/src/stories/Laboratory/Cards/DocumentCard/DocumentCardItem.stories.ts +119 -0
  134. package/src/stories/Laboratory/Cards/InfoCard/InfoCard.stories.ts +320 -0
  135. package/src/stories/Laboratory/Cards/TicketCard/TicketCard.stories.ts +335 -0
  136. package/src/stories/Laboratory/Chat/ChatBoxImage.stories.ts +82 -0
  137. package/src/stories/Laboratory/Chat/ChatMessage.stories.ts +198 -0
  138. package/src/stories/Laboratory/Chat/ChatMessageBadge.stories.ts +204 -0
  139. package/src/stories/Laboratory/Chat/ChatNotification.stories.ts +144 -0
  140. package/src/stories/Laboratory/Chat/ProgressLinear.stories.ts +186 -0
  141. package/src/stories/Laboratory/Chat/StatusNotification.stories.ts +111 -0
  142. package/src/stories/Laboratory/MainColumnsBar.stories.ts +48 -0
  143. package/src/stories/Laboratory/ProgressCircle.stories.ts +261 -0
  144. package/src/stories/Laboratory/SelectionColumnBar.stories.ts +234 -0
  145. package/src/stories/Laboratory/TagLabel.stories.ts +418 -0
  146. package/src/stories/Laboratory/TagLabelGroup.stories.ts +234 -0
  147. package/src/stories/Laboratory/Timeline.stories.ts +403 -0
  148. package/src/stories/NotificationBubble.stories.ts +194 -0
  149. package/src/stories/OtpInput.stories.ts +101 -0
  150. package/src/stories/PhoneInput.stories.ts +53 -0
  151. package/src/stories/Select.stories.ts +419 -0
  152. package/src/stories/TextArea.stories.ts +112 -0
  153. package/src/stories/TickBox.stories.ts +294 -0
  154. package/src/stories/assets/accessibility.png +0 -0
  155. package/src/stories/assets/accessibility.svg +1 -0
  156. package/src/stories/assets/addon-library.png +0 -0
  157. package/src/stories/assets/assets.png +0 -0
  158. package/src/stories/assets/avif-test-image.avif +0 -0
  159. package/src/stories/assets/context.png +0 -0
  160. package/src/stories/assets/discord.svg +1 -0
  161. package/src/stories/assets/docs.png +0 -0
  162. package/src/stories/assets/figma-plugin.png +0 -0
  163. package/src/stories/assets/github.svg +1 -0
  164. package/src/stories/assets/share.png +0 -0
  165. package/src/stories/assets/styling.png +0 -0
  166. package/src/stories/assets/testing.png +0 -0
  167. package/src/stories/assets/theming.png +0 -0
  168. package/src/stories/assets/tutorials.svg +1 -0
  169. package/src/stories/assets/youtube.svg +1 -0
  170. package/src/stories/v-icon.stories.ts +91 -0
  171. package/src/types/index.ts +21 -0
  172. package/src/vite-env.d.ts +1 -0
  173. package/tests/e2e/README.md +221 -0
  174. package/tests/e2e/accessibility.spec.ts +639 -0
  175. package/tests/e2e/accordion.spec.ts +42 -0
  176. package/tests/e2e/additional-components.spec.ts +438 -0
  177. package/tests/e2e/all-components.spec.ts +135 -0
  178. package/tests/e2e/button-fixed.spec.ts +59 -0
  179. package/tests/e2e/button.spec.ts +76 -0
  180. package/tests/e2e/checkbox.spec.ts +50 -0
  181. package/tests/e2e/date-input.spec.ts +46 -0
  182. package/tests/e2e/debug.spec.ts +52 -0
  183. package/tests/e2e/dialog.spec.ts +58 -0
  184. package/tests/e2e/input.spec.ts +55 -0
  185. package/tests/e2e/laboratory-components.spec.ts +321 -0
  186. package/tests/e2e/otp-input.spec.ts +50 -0
  187. package/tests/e2e/select.spec.ts +52 -0
  188. package/tests/e2e/storybook-utils.ts +59 -0
  189. package/tests/e2e/test-basic.spec.ts +34 -0
  190. package/tests/e2e/visual-regression.spec.ts +351 -0
  191. package/tests/unit/components/Accordion/AccordionGroup.spec.ts +343 -0
  192. package/tests/unit/components/Accordion/AccordionItem.spec.ts +384 -0
  193. package/tests/unit/components/Audio/Audio.spec.ts +404 -0
  194. package/tests/unit/components/Audio/Waveform.spec.ts +484 -0
  195. package/tests/unit/components/Core/Button.spec.ts +337 -0
  196. package/tests/unit/components/Core/Checkbox.spec.ts +545 -0
  197. package/tests/unit/components/Core/DateInput.spec.ts +691 -0
  198. package/tests/unit/components/Core/Dialog.spec.ts +486 -0
  199. package/tests/unit/components/Core/EditField.spec.ts +783 -0
  200. package/tests/unit/components/Core/Input.spec.ts +513 -0
  201. package/tests/unit/components/Core/Modal.spec.ts +519 -0
  202. package/tests/unit/components/Core/NotificationBubble.spec.ts +607 -0
  203. package/tests/unit/components/Core/OtpInput.spec.ts +709 -0
  204. package/tests/unit/components/Core/PhoneInput.spec.ts +620 -0
  205. package/tests/unit/components/Core/Select.spec.ts +713 -0
  206. package/tests/unit/components/Core/TextArea.spec.ts +566 -0
  207. package/tests/unit/components/Core/TickBox.spec.ts +780 -0
  208. package/tests/unit/components/IconBullet/IconBullet.spec.ts +357 -0
  209. package/tests/unit/components/IconBullet/IconBulletList.spec.ts +372 -0
  210. package/tests/unit/components/Icons/Audio/CloudFailed.spec.ts +109 -0
  211. package/tests/unit/components/Icons/Audio/CloudSaved.spec.ts +150 -0
  212. package/tests/unit/components/Icons/Audio/Delete.spec.ts +159 -0
  213. package/tests/unit/components/Icons/Audio/Pause.spec.ts +209 -0
  214. package/tests/unit/components/Icons/Audio/Play.spec.ts +218 -0
  215. package/tests/unit/components/Icons/CalendarNotification.spec.ts +187 -0
  216. package/tests/unit/components/Icons/Chair.spec.ts +235 -0
  217. package/tests/unit/components/Icons/ChairNotification.spec.ts +312 -0
  218. package/tests/unit/components/Icons/Circle.spec.ts +256 -0
  219. package/tests/unit/components/Icons/FavIcon.spec.ts +252 -0
  220. package/tests/unit/components/Icons/FilledCircle.spec.ts +275 -0
  221. package/tests/unit/components/Icons/Group3.spec.ts +356 -0
  222. package/tests/unit/components/Icons/RingNotification.spec.ts +394 -0
  223. package/tests/unit/components/Icons/calendar.spec.ts +287 -0
  224. package/tests/unit/components/Icons/checkbox.spec.ts +316 -0
  225. package/tests/unit/components/Icons/outlineChecked.spec.ts +435 -0
  226. package/tests/unit/components/Icons/play.spec.ts +309 -0
  227. package/tests/unit/components/Laboratory/AppointmentCard.spec.ts +168 -0
  228. package/tests/unit/components/Laboratory/ChatBoxImage.spec.ts +180 -0
  229. package/tests/unit/components/Laboratory/ChatMessage.spec.ts +264 -0
  230. package/tests/unit/components/Laboratory/ChatMessageBadge.spec.ts +283 -0
  231. package/tests/unit/components/Laboratory/ChatNotification.spec.ts +257 -0
  232. package/tests/unit/components/Laboratory/DocumentCard.spec.ts +229 -0
  233. package/tests/unit/components/Laboratory/DocumentCardItem.spec.ts +237 -0
  234. package/tests/unit/components/Laboratory/InfoCard.spec.ts +309 -0
  235. package/tests/unit/components/Laboratory/MainColumnsBar.spec.ts +252 -0
  236. package/tests/unit/components/Laboratory/ProgressCircle.spec.ts +291 -0
  237. package/tests/unit/components/Laboratory/ProgressLinear.spec.ts +276 -0
  238. package/tests/unit/components/Laboratory/SelectionColumnBar.spec.ts +289 -0
  239. package/tests/unit/components/Laboratory/StatusNotification.spec.ts +297 -0
  240. package/tests/unit/components/Laboratory/TagLabel.spec.ts +354 -0
  241. package/tests/unit/components/Laboratory/TagLabelGroup.spec.ts +378 -0
  242. package/tests/unit/components/Laboratory/TicketCard.spec.ts +352 -0
  243. package/tests/unit/components/Laboratory/TimeLineEvent.spec.ts +382 -0
  244. package/tests/unit/components/Laboratory/Timeline.spec.ts +420 -0
  245. package/tests/unit/constants/iconEnums.spec.ts +40 -0
  246. package/tests/unit/i18n/i18n.spec.ts +89 -0
  247. package/tests/unit/plugins/vuetify.spec.ts +221 -0
  248. package/tests/unit/setup.ts +190 -0
  249. package/tests/unit/src/components/index.spec.ts +193 -0
  250. package/tests/unit/src/index.spec.ts +183 -0
  251. package/tests/unit/src/main.spec.ts +152 -0
  252. package/tsconfig.json +26 -0
  253. package/vite.config.ts +29 -0
  254. package/vitest.config.ts +84 -0
@@ -0,0 +1,484 @@
1
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
2
+ import { mount, VueWrapper } from '@vue/test-utils';
3
+ import { nextTick } from 'vue';
4
+ import Waveform from '@components/Audio/Waveform.vue';
5
+
6
+ // Mock WaveSurfer with proper hoisting
7
+ vi.mock('wavesurfer.js', () => {
8
+ const mockWsInstance = {
9
+ on: vi.fn(),
10
+ play: vi.fn(),
11
+ pause: vi.fn(),
12
+ loadBlob: vi.fn(),
13
+ load: vi.fn(),
14
+ getCurrentTime: vi.fn(() => 0),
15
+ destroy: vi.fn()
16
+ };
17
+
18
+ const mockWaveSurfer = {
19
+ create: vi.fn(() => mockWsInstance),
20
+ on: vi.fn(),
21
+ play: vi.fn(),
22
+ pause: vi.fn(),
23
+ loadBlob: vi.fn(),
24
+ load: vi.fn(),
25
+ getCurrentTime: vi.fn(() => 0),
26
+ destroy: vi.fn()
27
+ };
28
+
29
+ return {
30
+ default: mockWaveSurfer,
31
+ __mockWsInstance: mockWsInstance,
32
+ __mockWaveSurfer: mockWaveSurfer
33
+ };
34
+ });
35
+
36
+ // Mock getComputedStyle
37
+ Object.defineProperty(window, 'getComputedStyle', {
38
+ value: vi.fn(() => ({
39
+ getPropertyValue: vi.fn((prop: string) => {
40
+ if (prop === '--Dental-Light-Blue-2') return '#93B1CF';
41
+ if (prop === '--Dental-Blue-0') return '#172774';
42
+ return '';
43
+ })
44
+ }))
45
+ });
46
+
47
+ describe('Waveform', () => {
48
+ let wrapper: VueWrapper;
49
+ let mockWaveSurfer: any;
50
+ let mockWsInstance: any;
51
+
52
+ beforeEach(async () => {
53
+ vi.clearAllMocks();
54
+
55
+ // Get the mock instances from the mocked module
56
+ const WaveSurferMock = await import('wavesurfer.js');
57
+ mockWaveSurfer = (WaveSurferMock as any).__mockWaveSurfer;
58
+ mockWsInstance = (WaveSurferMock as any).__mockWsInstance;
59
+
60
+ mockWaveSurfer.create.mockReturnValue(mockWsInstance);
61
+
62
+ // Mock DOM element dimensions
63
+ Object.defineProperty(HTMLElement.prototype, 'clientWidth', {
64
+ configurable: true,
65
+ value: 400
66
+ });
67
+ Object.defineProperty(HTMLElement.prototype, 'clientHeight', {
68
+ configurable: true,
69
+ value: 50
70
+ });
71
+ });
72
+
73
+ afterEach(() => {
74
+ if (wrapper) {
75
+ wrapper.unmount();
76
+ }
77
+ });
78
+
79
+ describe('Rendering', () => {
80
+ it('renders waveform container div', () => {
81
+ wrapper = mount(Waveform, {
82
+ props: { src: 'test.mp3' }
83
+ });
84
+
85
+ expect(wrapper.find('.waveform').exists()).toBe(true);
86
+ });
87
+
88
+ it('applies correct CSS classes', () => {
89
+ wrapper = mount(Waveform, {
90
+ props: { src: 'test.mp3' }
91
+ });
92
+
93
+ const waveformDiv = wrapper.find('.waveform');
94
+ expect(waveformDiv.exists()).toBe(true);
95
+ });
96
+ });
97
+
98
+ describe('Props', () => {
99
+ it('accepts src prop', () => {
100
+ wrapper = mount(Waveform, {
101
+ props: { src: 'test-audio.mp3' }
102
+ });
103
+
104
+ expect(wrapper.props('src')).toBe('test-audio.mp3');
105
+ });
106
+
107
+ it('accepts blob prop', () => {
108
+ const blob = new Blob(['test'], { type: 'audio/wav' });
109
+ wrapper = mount(Waveform, {
110
+ props: { blob }
111
+ });
112
+
113
+ expect(wrapper.props('blob')).toStrictEqual(blob);
114
+ });
115
+
116
+ it('can be mounted without props', () => {
117
+ expect(() => {
118
+ wrapper = mount(Waveform);
119
+ }).not.toThrow();
120
+ });
121
+ });
122
+
123
+ describe('WaveSurfer Integration', () => {
124
+ it('creates WaveSurfer instance on mount', async () => {
125
+ wrapper = mount(Waveform, {
126
+ props: { src: 'test.mp3' }
127
+ });
128
+
129
+ await nextTick();
130
+
131
+ expect(mockWaveSurfer.create).toHaveBeenCalledWith({
132
+ container: expect.any(Object),
133
+ waveColor: '#93B1CF',
134
+ progressColor: '#172774',
135
+ barWidth: 2,
136
+ barGap: 2,
137
+ barRadius: 4,
138
+ cursorWidth: 0,
139
+ height: 50,
140
+ width: 400
141
+ });
142
+ });
143
+
144
+ it('loads audio from src prop', async () => {
145
+ wrapper = mount(Waveform, {
146
+ props: { src: 'test-audio.mp3' }
147
+ });
148
+
149
+ await nextTick();
150
+
151
+ expect(mockWsInstance.load).toHaveBeenCalledWith('test-audio.mp3');
152
+ expect(mockWsInstance.loadBlob).not.toHaveBeenCalled();
153
+ });
154
+
155
+ it('loads audio from blob prop', async () => {
156
+ const blob = new Blob(['test'], { type: 'audio/wav' });
157
+ wrapper = mount(Waveform, {
158
+ props: { blob }
159
+ });
160
+
161
+ await nextTick();
162
+
163
+ expect(mockWsInstance.loadBlob).toHaveBeenCalledWith(blob);
164
+ expect(mockWsInstance.load).not.toHaveBeenCalled();
165
+ });
166
+
167
+ it('prioritizes blob over src when both provided', async () => {
168
+ const blob = new Blob(['test'], { type: 'audio/wav' });
169
+ wrapper = mount(Waveform, {
170
+ props: { src: 'test.mp3', blob }
171
+ });
172
+
173
+ await nextTick();
174
+
175
+ expect(mockWsInstance.loadBlob).toHaveBeenCalledWith(blob);
176
+ expect(mockWsInstance.load).not.toHaveBeenCalled();
177
+ });
178
+
179
+ it('handles missing audio source', async () => {
180
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
181
+
182
+ wrapper = mount(Waveform);
183
+ await nextTick();
184
+
185
+ // Should handle the error gracefully
186
+ expect(wrapper.find('.waveform').exists()).toBe(true);
187
+
188
+ consoleSpy.mockRestore();
189
+ });
190
+ });
191
+
192
+ describe('Event Handling', () => {
193
+ beforeEach(async () => {
194
+ wrapper = mount(Waveform, {
195
+ props: { src: 'test.mp3' }
196
+ });
197
+ await nextTick();
198
+ });
199
+
200
+ it('sets up WaveSurfer event listeners', () => {
201
+ expect(mockWsInstance.on).toHaveBeenCalledWith('audioprocess', expect.any(Function));
202
+ expect(mockWsInstance.on).toHaveBeenCalledWith('finish', expect.any(Function));
203
+ expect(mockWsInstance.on).toHaveBeenCalledWith('ready', expect.any(Function));
204
+ expect(mockWsInstance.on).toHaveBeenCalledWith('error', expect.any(Function));
205
+ });
206
+
207
+ it('emits timeupdate when audioprocess event fires', () => {
208
+ const audioprocessCallback = mockWsInstance.on.mock.calls.find(call => call[0] === 'audioprocess')[1];
209
+
210
+ audioprocessCallback(30);
211
+
212
+ expect(wrapper.emitted('timeupdate')).toBeTruthy();
213
+ expect(wrapper.emitted('timeupdate')[0]).toEqual([30]);
214
+ });
215
+
216
+ it('emits finish when finish event fires', () => {
217
+ const finishCallback = mockWsInstance.on.mock.calls.find(call => call[0] === 'finish')[1];
218
+
219
+ finishCallback();
220
+
221
+ expect(wrapper.emitted('finish')).toBeTruthy();
222
+ });
223
+
224
+ it('emits ready when ready event fires', () => {
225
+ const readyCallback = mockWsInstance.on.mock.calls.find(call => call[0] === 'ready')[1];
226
+
227
+ readyCallback(120);
228
+
229
+ expect(wrapper.emitted('ready')).toBeTruthy();
230
+ expect(wrapper.emitted('ready')[0]).toEqual([120]);
231
+ });
232
+
233
+ it('handles error events', () => {
234
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
235
+ const errorCallback = mockWsInstance.on.mock.calls.find(call => call[0] === 'error')[1];
236
+
237
+ const testError = new Error('Test error');
238
+ errorCallback(testError);
239
+
240
+ expect(consoleSpy).toHaveBeenCalledWith(testError);
241
+
242
+ consoleSpy.mockRestore();
243
+ });
244
+ });
245
+
246
+ describe('Exposed Methods', () => {
247
+ beforeEach(async () => {
248
+ wrapper = mount(Waveform, {
249
+ props: { src: 'test.mp3' }
250
+ });
251
+ await nextTick();
252
+ });
253
+
254
+ it('exposes play method', () => {
255
+ expect(wrapper.vm.play).toBeInstanceOf(Function);
256
+
257
+ wrapper.vm.play();
258
+
259
+ expect(mockWsInstance.play).toHaveBeenCalled();
260
+ });
261
+
262
+ it('exposes pause method', () => {
263
+ expect(wrapper.vm.pause).toBeInstanceOf(Function);
264
+
265
+ wrapper.vm.pause();
266
+
267
+ expect(mockWsInstance.pause).toHaveBeenCalled();
268
+ });
269
+
270
+ it('exposes getCurrentTime method', () => {
271
+ expect(wrapper.vm.getCurrentTime).toBeInstanceOf(Function);
272
+
273
+ mockWsInstance.getCurrentTime.mockReturnValue(45);
274
+
275
+ const currentTime = wrapper.vm.getCurrentTime();
276
+
277
+ expect(mockWsInstance.getCurrentTime).toHaveBeenCalled();
278
+ expect(currentTime).toBe(45);
279
+ });
280
+
281
+ it('handles play when WaveSurfer instance not ready', () => {
282
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
283
+
284
+ // Create wrapper without initializing WaveSurfer
285
+ wrapper = mount(Waveform);
286
+ wrapper.vm.ws = null;
287
+
288
+ wrapper.vm.play();
289
+
290
+ expect(consoleSpy).toHaveBeenCalledWith('WaveSurfer instance not ready');
291
+
292
+ consoleSpy.mockRestore();
293
+ });
294
+
295
+ it('handles pause when WaveSurfer instance not ready', () => {
296
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
297
+
298
+ wrapper = mount(Waveform);
299
+ wrapper.vm.ws = null;
300
+
301
+ wrapper.vm.pause();
302
+
303
+ expect(consoleSpy).toHaveBeenCalledWith('WaveSurfer instance not ready');
304
+
305
+ consoleSpy.mockRestore();
306
+ });
307
+
308
+ it('returns 0 for getCurrentTime when WaveSurfer instance not ready', () => {
309
+ wrapper = mount(Waveform);
310
+ wrapper.vm.ws = null;
311
+
312
+ const currentTime = wrapper.vm.getCurrentTime();
313
+
314
+ expect(currentTime).toBe(0);
315
+ });
316
+ });
317
+
318
+ describe('CSS Variable Integration', () => {
319
+ it('reads CSS variables for colors', async () => {
320
+ wrapper = mount(Waveform, {
321
+ props: { src: 'test.mp3' }
322
+ });
323
+
324
+ await nextTick();
325
+
326
+ expect(window.getComputedStyle).toHaveBeenCalledWith(document.documentElement);
327
+ expect(mockWaveSurfer.create).toHaveBeenCalledWith(
328
+ expect.objectContaining({
329
+ waveColor: '#93B1CF',
330
+ progressColor: '#172774'
331
+ })
332
+ );
333
+ });
334
+
335
+ it('handles missing CSS variables gracefully', async () => {
336
+ const mockGetComputedStyle = vi.fn(() => ({
337
+ getPropertyValue: vi.fn(() => '')
338
+ }));
339
+
340
+ Object.defineProperty(window, 'getComputedStyle', {
341
+ value: mockGetComputedStyle,
342
+ configurable: true
343
+ });
344
+
345
+ wrapper = mount(Waveform, {
346
+ props: { src: 'test.mp3' }
347
+ });
348
+
349
+ await nextTick();
350
+
351
+ expect(mockWaveSurfer.create).toHaveBeenCalledWith(
352
+ expect.objectContaining({
353
+ waveColor: '',
354
+ progressColor: ''
355
+ })
356
+ );
357
+ });
358
+ });
359
+
360
+ describe('Component Lifecycle', () => {
361
+ it('destroys WaveSurfer instance on unmount', async () => {
362
+ wrapper = mount(Waveform, {
363
+ props: { src: 'test.mp3' }
364
+ });
365
+
366
+ await nextTick();
367
+
368
+ wrapper.unmount();
369
+
370
+ expect(mockWsInstance.destroy).toHaveBeenCalled();
371
+ });
372
+
373
+ it('handles unmount when WaveSurfer instance is null', async () => {
374
+ wrapper = mount(Waveform);
375
+
376
+ expect(() => {
377
+ wrapper.unmount();
378
+ }).not.toThrow();
379
+ });
380
+ });
381
+
382
+ describe('Error Handling', () => {
383
+ it('handles WaveSurfer creation errors', async () => {
384
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
385
+ mockWaveSurfer.create.mockImplementation(() => {
386
+ throw new Error('WaveSurfer creation failed');
387
+ });
388
+
389
+ wrapper = mount(Waveform, {
390
+ props: { src: 'test.mp3' }
391
+ });
392
+
393
+ await nextTick();
394
+
395
+ // Component should still render despite the error
396
+ expect(wrapper.find('.waveform').exists()).toBe(true);
397
+
398
+ consoleSpy.mockRestore();
399
+ });
400
+
401
+ it('handles load errors gracefully', async () => {
402
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
403
+ mockWsInstance.load.mockImplementation(() => {
404
+ throw new Error('Load failed');
405
+ });
406
+
407
+ wrapper = mount(Waveform, {
408
+ props: { src: 'invalid-url' }
409
+ });
410
+
411
+ await nextTick();
412
+
413
+ expect(consoleSpy).toHaveBeenCalledWith(
414
+ 'Error initializing WaveSurfer:',
415
+ expect.any(Error)
416
+ );
417
+
418
+ consoleSpy.mockRestore();
419
+ });
420
+
421
+ it('handles loadBlob errors gracefully', async () => {
422
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
423
+ mockWsInstance.loadBlob.mockImplementation(() => {
424
+ throw new Error('Load blob failed');
425
+ });
426
+
427
+ const invalidBlob = new Blob(['invalid'], { type: 'text/plain' });
428
+ wrapper = mount(Waveform, {
429
+ props: { blob: invalidBlob }
430
+ });
431
+
432
+ await nextTick();
433
+
434
+ expect(consoleSpy).toHaveBeenCalledWith(
435
+ 'Error initializing WaveSurfer:',
436
+ expect.any(Error)
437
+ );
438
+
439
+ consoleSpy.mockRestore();
440
+ });
441
+ });
442
+
443
+ describe('Component Integration', () => {
444
+ it('works with different container sizes', async () => {
445
+ Object.defineProperty(HTMLElement.prototype, 'clientWidth', {
446
+ configurable: true,
447
+ value: 800
448
+ });
449
+ Object.defineProperty(HTMLElement.prototype, 'clientHeight', {
450
+ configurable: true,
451
+ value: 100
452
+ });
453
+
454
+ wrapper = mount(Waveform, {
455
+ props: { src: 'test.mp3' }
456
+ });
457
+
458
+ await nextTick();
459
+
460
+ expect(mockWaveSurfer.create).toHaveBeenCalledWith(
461
+ expect.objectContaining({
462
+ width: 800,
463
+ height: 100
464
+ })
465
+ );
466
+ });
467
+
468
+ it('renders consistently on multiple mounts', () => {
469
+ const wrapper1 = mount(Waveform, {
470
+ props: { src: 'test.mp3' }
471
+ });
472
+
473
+ const wrapper2 = mount(Waveform, {
474
+ props: { src: 'test.mp3' }
475
+ });
476
+
477
+ expect(wrapper1.find('.waveform').exists()).toBe(true);
478
+ expect(wrapper2.find('.waveform').exists()).toBe(true);
479
+
480
+ wrapper1.unmount();
481
+ wrapper2.unmount();
482
+ });
483
+ });
484
+ });