@getmicdrop/svelte-components 5.17.1 → 5.17.4

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 (199) hide show
  1. package/dist/calendar/Calendar/MiniMonthCalendar.svelte +5 -7
  2. package/dist/calendar/Calendar/MiniMonthCalendar.svelte.d.ts.map +1 -1
  3. package/dist/calendar/MonthSwitcher/MonthSwitcher.svelte +2 -3
  4. package/dist/calendar/MonthSwitcher/MonthSwitcher.svelte.d.ts.map +1 -1
  5. package/dist/calendar/PublicCard/PublicCard.svelte +23 -14
  6. package/dist/calendar/PublicCard/PublicCard.svelte.d.ts.map +1 -1
  7. package/dist/calendar/ShowCard/ShowCard.spec.js +1 -7
  8. package/dist/calendar/ShowCard/ShowCard.svelte +10 -1
  9. package/dist/calendar/ShowCard/ShowCard.svelte.d.ts.map +1 -1
  10. package/dist/calendar/ShowTimeCard/ShowTimeCard.svelte +11 -0
  11. package/dist/calendar/ShowTimeCard/ShowTimeCard.svelte.d.ts +2 -0
  12. package/dist/calendar/ShowTimeCard/ShowTimeCard.svelte.d.ts.map +1 -1
  13. package/dist/components/Heading.spec.d.ts +2 -0
  14. package/dist/components/Heading.spec.d.ts.map +1 -0
  15. package/dist/components/Heading.spec.js +89 -0
  16. package/dist/components/Layout/__tests__/AppShell.test.js +140 -0
  17. package/dist/components/Text.spec.d.ts +2 -0
  18. package/dist/components/Text.spec.d.ts.map +1 -0
  19. package/dist/components/Text.spec.js +89 -0
  20. package/dist/config.d.ts +102 -0
  21. package/dist/config.js +147 -1
  22. package/dist/datetime/README.md +323 -0
  23. package/dist/forms/createFormStore.svelte.spec.d.ts +2 -0
  24. package/dist/forms/createFormStore.svelte.spec.d.ts.map +1 -0
  25. package/dist/forms/createFormStore.svelte.spec.js +387 -0
  26. package/dist/messages.d.ts +43 -0
  27. package/dist/messages.d.ts.map +1 -0
  28. package/dist/messages.js +57 -0
  29. package/dist/patterns/chat/ChatActivityNotice.spec.d.ts +2 -0
  30. package/dist/patterns/chat/ChatActivityNotice.spec.d.ts.map +1 -0
  31. package/dist/patterns/chat/ChatActivityNotice.spec.js +59 -0
  32. package/dist/patterns/chat/ChatBubble.spec.d.ts +2 -0
  33. package/dist/patterns/chat/ChatBubble.spec.d.ts.map +1 -0
  34. package/dist/patterns/chat/ChatBubble.spec.js +91 -0
  35. package/dist/patterns/chat/ChatContainer.spec.d.ts +2 -0
  36. package/dist/patterns/chat/ChatContainer.spec.d.ts.map +1 -0
  37. package/dist/patterns/chat/ChatContainer.spec.js +30 -0
  38. package/dist/patterns/chat/ChatDateDivider.spec.d.ts +2 -0
  39. package/dist/patterns/chat/ChatDateDivider.spec.d.ts.map +1 -0
  40. package/dist/patterns/chat/ChatDateDivider.spec.js +30 -0
  41. package/dist/patterns/chat/ChatInvitationBubble.spec.d.ts +2 -0
  42. package/dist/patterns/chat/ChatInvitationBubble.spec.d.ts.map +1 -0
  43. package/dist/patterns/chat/ChatInvitationBubble.spec.js +46 -0
  44. package/dist/patterns/chat/ChatInvitationNotice.spec.d.ts +2 -0
  45. package/dist/patterns/chat/ChatInvitationNotice.spec.d.ts.map +1 -0
  46. package/dist/patterns/chat/ChatInvitationNotice.spec.js +32 -0
  47. package/dist/patterns/chat/ChatMessageGroup.spec.d.ts +2 -0
  48. package/dist/patterns/chat/ChatMessageGroup.spec.d.ts.map +1 -0
  49. package/dist/patterns/chat/ChatMessageGroup.spec.js +58 -0
  50. package/dist/patterns/chat/ChatSlotUpdate.spec.d.ts +2 -0
  51. package/dist/patterns/chat/ChatSlotUpdate.spec.d.ts.map +1 -0
  52. package/dist/patterns/chat/ChatSlotUpdate.spec.js +65 -0
  53. package/dist/patterns/chat/ChatStatusBadge.spec.d.ts +2 -0
  54. package/dist/patterns/chat/ChatStatusBadge.spec.d.ts.map +1 -0
  55. package/dist/patterns/chat/ChatStatusBadge.spec.js +79 -0
  56. package/dist/patterns/chat/ChatStatusTransition.spec.d.ts +2 -0
  57. package/dist/patterns/chat/ChatStatusTransition.spec.d.ts.map +1 -0
  58. package/dist/patterns/chat/ChatStatusTransition.spec.js +81 -0
  59. package/dist/patterns/chat/ChatTextBubble.spec.d.ts +2 -0
  60. package/dist/patterns/chat/ChatTextBubble.spec.d.ts.map +1 -0
  61. package/dist/patterns/chat/ChatTextBubble.spec.js +35 -0
  62. package/dist/patterns/data/DataTable.spec.js +61 -0
  63. package/dist/patterns/forms/FormGrid.spec.js +34 -0
  64. package/dist/patterns/layout/Sidebar.spec.js +240 -1
  65. package/dist/patterns/layout/SidebarTestWrapper.svelte +34 -0
  66. package/dist/patterns/layout/SidebarTestWrapper.svelte.d.ts +23 -0
  67. package/dist/patterns/layout/SidebarTestWrapper.svelte.d.ts.map +1 -0
  68. package/dist/patterns/navigation/Header.spec.js +123 -0
  69. package/dist/primitives/Accordion/Accordion.spec.js +112 -2
  70. package/dist/primitives/Accordion/AccordionToggleWrapper.test.svelte +28 -0
  71. package/dist/primitives/Accordion/AccordionToggleWrapper.test.svelte.d.ts +7 -0
  72. package/dist/primitives/Accordion/AccordionToggleWrapper.test.svelte.d.ts.map +1 -0
  73. package/dist/primitives/Avatar/Avatar.spec.js +23 -0
  74. package/dist/primitives/BottomSheet/BottomSheet.spec.js +102 -0
  75. package/dist/primitives/BottomSheet/BottomSheetWithActions.test.svelte +20 -0
  76. package/dist/primitives/BottomSheet/BottomSheetWithActions.test.svelte.d.ts +10 -0
  77. package/dist/primitives/BottomSheet/BottomSheetWithActions.test.svelte.d.ts.map +1 -0
  78. package/dist/primitives/Button/ButtonGroup.spec.d.ts +2 -0
  79. package/dist/primitives/Button/ButtonGroup.spec.d.ts.map +1 -0
  80. package/dist/primitives/Button/ButtonGroup.spec.js +44 -0
  81. package/dist/primitives/Checkbox/Checkbox.spec.js +32 -0
  82. package/dist/primitives/Drawer/Drawer.spec.js +437 -0
  83. package/dist/primitives/Drawer/DrawerTestWrapper.svelte +86 -0
  84. package/dist/primitives/Drawer/DrawerTestWrapper.svelte.d.ts +26 -0
  85. package/dist/primitives/Drawer/DrawerTestWrapper.svelte.d.ts.map +1 -0
  86. package/dist/primitives/Dropdown/Dropdown.spec.js +116 -0
  87. package/dist/primitives/Dropdown/DropdownDivider.spec.d.ts +2 -0
  88. package/dist/primitives/Dropdown/DropdownDivider.spec.d.ts.map +1 -0
  89. package/dist/primitives/Dropdown/DropdownDivider.spec.js +30 -0
  90. package/dist/primitives/Dropdown/DropdownItem.spec.js +155 -1
  91. package/dist/primitives/Dropdown/DropdownItemTestWrapper.svelte +43 -0
  92. package/dist/primitives/Dropdown/DropdownItemTestWrapper.svelte.d.ts +17 -0
  93. package/dist/primitives/Dropdown/DropdownItemTestWrapper.svelte.d.ts.map +1 -0
  94. package/dist/primitives/Helper/Helper.spec.d.ts +2 -0
  95. package/dist/primitives/Helper/Helper.spec.d.ts.map +1 -0
  96. package/dist/primitives/Helper/Helper.spec.js +57 -0
  97. package/dist/primitives/Input/Input.spec.js +664 -0
  98. package/dist/primitives/Input/Input.svelte +18 -10
  99. package/dist/primitives/Input/Input.svelte.d.ts.map +1 -1
  100. package/dist/primitives/Input/Select.spec.js +414 -0
  101. package/dist/primitives/Label/Label.spec.js +9 -0
  102. package/dist/primitives/LandingButton/LandingButton.spec.d.ts +2 -0
  103. package/dist/primitives/LandingButton/LandingButton.spec.d.ts.map +1 -0
  104. package/dist/primitives/LandingButton/LandingButton.spec.js +61 -0
  105. package/dist/primitives/MenuItem/MenuItem.spec.d.ts +2 -0
  106. package/dist/primitives/MenuItem/MenuItem.spec.d.ts.map +1 -0
  107. package/dist/primitives/MenuItem/MenuItem.spec.js +130 -0
  108. package/dist/primitives/Modal/Modal.spec.js +215 -0
  109. package/dist/primitives/NavItem/NavItem.spec.d.ts +2 -0
  110. package/dist/primitives/NavItem/NavItem.spec.d.ts.map +1 -0
  111. package/dist/primitives/NavItem/NavItem.spec.js +97 -0
  112. package/dist/primitives/SearchResultItem/SearchResultItem.spec.d.ts +2 -0
  113. package/dist/primitives/SearchResultItem/SearchResultItem.spec.d.ts.map +1 -0
  114. package/dist/primitives/SearchResultItem/SearchResultItem.spec.js +78 -0
  115. package/dist/primitives/SidebarToggle/SidebarToggle.spec.d.ts +2 -0
  116. package/dist/primitives/SidebarToggle/SidebarToggle.spec.d.ts.map +1 -0
  117. package/dist/primitives/SidebarToggle/SidebarToggle.spec.js +61 -0
  118. package/dist/primitives/Spinner/Spinner.spec.js +13 -0
  119. package/dist/primitives/Toggle.spec.js +75 -0
  120. package/dist/primitives/ToggleTestWrapper.svelte +30 -0
  121. package/dist/primitives/ToggleTestWrapper.svelte.d.ts +29 -0
  122. package/dist/primitives/ToggleTestWrapper.svelte.d.ts.map +1 -0
  123. package/dist/primitives/Tooltip/Tooltip.spec.d.ts +2 -0
  124. package/dist/primitives/Tooltip/Tooltip.spec.d.ts.map +1 -0
  125. package/dist/primitives/Tooltip/Tooltip.spec.js +126 -0
  126. package/dist/recipes/inputs/Search.spec.js +66 -2
  127. package/dist/recipes/modals/ConfirmationModal.spec.js +190 -0
  128. package/dist/schemas/__tests__/auth.test.d.ts +2 -0
  129. package/dist/schemas/__tests__/auth.test.d.ts.map +1 -0
  130. package/dist/schemas/__tests__/auth.test.js +210 -0
  131. package/dist/schemas/__tests__/common.test.d.ts +2 -0
  132. package/dist/schemas/__tests__/common.test.d.ts.map +1 -0
  133. package/dist/schemas/__tests__/common.test.js +340 -0
  134. package/dist/schemas/__tests__/domain.test.d.ts +2 -0
  135. package/dist/schemas/__tests__/domain.test.d.ts.map +1 -0
  136. package/dist/schemas/__tests__/domain.test.js +293 -0
  137. package/dist/schemas/__tests__/order.test.d.ts +2 -0
  138. package/dist/schemas/__tests__/order.test.d.ts.map +1 -0
  139. package/dist/schemas/__tests__/order.test.js +349 -0
  140. package/dist/schemas/__tests__/user.test.d.ts +2 -0
  141. package/dist/schemas/__tests__/user.test.d.ts.map +1 -0
  142. package/dist/schemas/__tests__/user.test.js +325 -0
  143. package/dist/schemas/auth.d.ts +41 -0
  144. package/dist/schemas/auth.d.ts.map +1 -0
  145. package/dist/schemas/auth.js +69 -0
  146. package/dist/schemas/common.d.ts +43 -0
  147. package/dist/schemas/common.d.ts.map +1 -0
  148. package/dist/schemas/common.js +157 -0
  149. package/dist/schemas/event.d.ts +82 -0
  150. package/dist/schemas/event.d.ts.map +1 -0
  151. package/dist/schemas/event.js +58 -0
  152. package/dist/schemas/index.d.ts +10 -0
  153. package/dist/schemas/index.d.ts.map +1 -0
  154. package/dist/schemas/index.js +9 -0
  155. package/dist/schemas/order.d.ts +111 -0
  156. package/dist/schemas/order.d.ts.map +1 -0
  157. package/dist/schemas/order.js +73 -0
  158. package/dist/schemas/performer.d.ts +133 -0
  159. package/dist/schemas/performer.d.ts.map +1 -0
  160. package/dist/schemas/performer.js +73 -0
  161. package/dist/schemas/promo.d.ts +87 -0
  162. package/dist/schemas/promo.d.ts.map +1 -0
  163. package/dist/schemas/promo.js +98 -0
  164. package/dist/schemas/ticket.d.ts +104 -0
  165. package/dist/schemas/ticket.d.ts.map +1 -0
  166. package/dist/schemas/ticket.js +82 -0
  167. package/dist/schemas/user.d.ts +92 -0
  168. package/dist/schemas/user.d.ts.map +1 -0
  169. package/dist/schemas/user.js +53 -0
  170. package/dist/schemas/venue.d.ts +95 -0
  171. package/dist/schemas/venue.d.ts.map +1 -0
  172. package/dist/schemas/venue.js +52 -0
  173. package/dist/stores/auth.svelte.spec.d.ts +2 -0
  174. package/dist/stores/auth.svelte.spec.d.ts.map +1 -0
  175. package/dist/stores/auth.svelte.spec.js +112 -0
  176. package/dist/stores/formDataStore.svelte.spec.d.ts +2 -0
  177. package/dist/stores/formDataStore.svelte.spec.d.ts.map +1 -0
  178. package/dist/stores/formDataStore.svelte.spec.js +150 -0
  179. package/dist/stores/formSave.svelte.spec.d.ts +2 -0
  180. package/dist/stores/formSave.svelte.spec.d.ts.map +1 -0
  181. package/dist/stores/formSave.svelte.spec.js +196 -0
  182. package/dist/stores/navigation.spec.d.ts +2 -0
  183. package/dist/stores/navigation.spec.d.ts.map +1 -0
  184. package/dist/stores/navigation.spec.js +53 -0
  185. package/dist/telemetry.spec.js +5 -0
  186. package/dist/tokens/__tests__/sizing.test.js +2 -2
  187. package/dist/tokens/sizing.d.ts +5 -0
  188. package/dist/tokens/sizing.d.ts.map +1 -1
  189. package/dist/tokens/sizing.js +6 -0
  190. package/dist/utils/haptic.spec.d.ts +2 -0
  191. package/dist/utils/haptic.spec.d.ts.map +1 -0
  192. package/dist/utils/haptic.spec.js +153 -0
  193. package/dist/utils/imageOptimizer.spec.d.ts +2 -0
  194. package/dist/utils/imageOptimizer.spec.d.ts.map +1 -0
  195. package/dist/utils/imageOptimizer.spec.js +201 -0
  196. package/dist/utils/logger.spec.d.ts +2 -0
  197. package/dist/utils/logger.spec.d.ts.map +1 -0
  198. package/dist/utils/logger.spec.js +95 -0
  199. package/package.json +1 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"haptic.spec.d.ts","sourceRoot":"","sources":["../../src/lib/utils/haptic.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,153 @@
1
+ import { describe, it, expect, vi, afterEach } from 'vitest';
2
+ import { triggerHaptic, isHapticAvailable, getHapticForButtonVariant } from './haptic';
3
+ describe('haptic', () => {
4
+ const originalWindow = globalThis.window;
5
+ afterEach(() => {
6
+ vi.restoreAllMocks();
7
+ // Clean up any added properties
8
+ // @ts-expect-error cleanup
9
+ delete window.webkit;
10
+ // @ts-expect-error cleanup
11
+ delete window.TapticEngine;
12
+ });
13
+ describe('triggerHaptic', () => {
14
+ it('returns early in non-browser environment', () => {
15
+ const windowSpy = vi.spyOn(globalThis, 'window', 'get');
16
+ // @ts-expect-error simulating server
17
+ windowSpy.mockReturnValue(undefined);
18
+ expect(() => triggerHaptic('light')).not.toThrow();
19
+ windowSpy.mockRestore();
20
+ });
21
+ it('uses iOS WebKit messageHandlers when available', () => {
22
+ const postMessage = vi.fn();
23
+ // @ts-expect-error mock iOS WebKit
24
+ window.webkit = { messageHandlers: { haptic: { postMessage } } };
25
+ triggerHaptic('success');
26
+ expect(postMessage).toHaveBeenCalledWith('success');
27
+ });
28
+ it('uses TapticEngine when available', () => {
29
+ const impact = vi.fn();
30
+ // @ts-expect-error mock TapticEngine
31
+ window.TapticEngine = { impact };
32
+ triggerHaptic('light');
33
+ expect(impact).toHaveBeenCalledWith({ style: 'light' });
34
+ });
35
+ it('maps success/warning/error/selection to medium for TapticEngine', () => {
36
+ const impact = vi.fn();
37
+ // @ts-expect-error mock TapticEngine
38
+ window.TapticEngine = { impact };
39
+ triggerHaptic('success');
40
+ expect(impact).toHaveBeenCalledWith({ style: 'medium' });
41
+ triggerHaptic('warning');
42
+ expect(impact).toHaveBeenCalledWith({ style: 'medium' });
43
+ triggerHaptic('error');
44
+ expect(impact).toHaveBeenCalledWith({ style: 'medium' });
45
+ triggerHaptic('selection');
46
+ expect(impact).toHaveBeenCalledWith({ style: 'medium' });
47
+ });
48
+ it('passes through light/medium/heavy to TapticEngine', () => {
49
+ const impact = vi.fn();
50
+ // @ts-expect-error mock TapticEngine
51
+ window.TapticEngine = { impact };
52
+ triggerHaptic('heavy');
53
+ expect(impact).toHaveBeenCalledWith({ style: 'heavy' });
54
+ triggerHaptic('medium');
55
+ expect(impact).toHaveBeenCalledWith({ style: 'medium' });
56
+ });
57
+ it('falls back to Vibration API', () => {
58
+ const vibrate = vi.fn();
59
+ Object.defineProperty(navigator, 'vibrate', { value: vibrate, writable: true, configurable: true });
60
+ triggerHaptic('light');
61
+ expect(vibrate).toHaveBeenCalledWith(10);
62
+ triggerHaptic('medium');
63
+ expect(vibrate).toHaveBeenCalledWith(20);
64
+ triggerHaptic('heavy');
65
+ expect(vibrate).toHaveBeenCalledWith(30);
66
+ triggerHaptic('success');
67
+ expect(vibrate).toHaveBeenCalledWith([10, 50, 20]);
68
+ triggerHaptic('warning');
69
+ expect(vibrate).toHaveBeenCalledWith([20, 40, 20]);
70
+ triggerHaptic('error');
71
+ expect(vibrate).toHaveBeenCalledWith([30, 50, 30, 50, 30]);
72
+ triggerHaptic('selection');
73
+ expect(vibrate).toHaveBeenCalledWith(8);
74
+ // cleanup
75
+ Object.defineProperty(navigator, 'vibrate', { value: undefined, writable: true, configurable: true });
76
+ });
77
+ it('defaults to light when no style specified', () => {
78
+ const vibrate = vi.fn();
79
+ Object.defineProperty(navigator, 'vibrate', { value: vibrate, writable: true, configurable: true });
80
+ triggerHaptic();
81
+ expect(vibrate).toHaveBeenCalledWith(10);
82
+ Object.defineProperty(navigator, 'vibrate', { value: undefined, writable: true, configurable: true });
83
+ });
84
+ it('does nothing when no haptic API available', () => {
85
+ // No webkit, no TapticEngine, no vibrate
86
+ Object.defineProperty(navigator, 'vibrate', { value: undefined, writable: true, configurable: true });
87
+ expect(() => triggerHaptic('light')).not.toThrow();
88
+ });
89
+ });
90
+ describe('isHapticAvailable', () => {
91
+ it('returns false in non-browser environment', () => {
92
+ const windowSpy = vi.spyOn(globalThis, 'window', 'get');
93
+ // @ts-expect-error simulating server
94
+ windowSpy.mockReturnValue(undefined);
95
+ expect(isHapticAvailable()).toBe(false);
96
+ windowSpy.mockRestore();
97
+ });
98
+ it('returns true when webkit haptic handler exists', () => {
99
+ // @ts-expect-error mock
100
+ window.webkit = { messageHandlers: { haptic: { postMessage: vi.fn() } } };
101
+ expect(isHapticAvailable()).toBe(true);
102
+ });
103
+ it('returns true when TapticEngine exists', () => {
104
+ // @ts-expect-error mock
105
+ window.TapticEngine = { impact: vi.fn() };
106
+ expect(isHapticAvailable()).toBe(true);
107
+ });
108
+ it('returns true when vibrate exists', () => {
109
+ Object.defineProperty(navigator, 'vibrate', { value: vi.fn(), writable: true, configurable: true });
110
+ expect(isHapticAvailable()).toBe(true);
111
+ Object.defineProperty(navigator, 'vibrate', { value: undefined, writable: true, configurable: true });
112
+ });
113
+ it('returns false when no haptic API available', () => {
114
+ Object.defineProperty(navigator, 'vibrate', { value: undefined, writable: true, configurable: true });
115
+ expect(isHapticAvailable()).toBe(false);
116
+ });
117
+ });
118
+ describe('getHapticForButtonVariant', () => {
119
+ it('returns success when isSuccess is true regardless of variant', () => {
120
+ expect(getHapticForButtonVariant('default', true)).toBe('success');
121
+ expect(getHapticForButtonVariant('ghost', true)).toBe('success');
122
+ });
123
+ it('returns heavy for destructive variants', () => {
124
+ expect(getHapticForButtonVariant('red')).toBe('heavy');
125
+ expect(getHapticForButtonVariant('red-outline')).toBe('heavy');
126
+ expect(getHapticForButtonVariant('ghost-red')).toBe('heavy');
127
+ expect(getHapticForButtonVariant('menu-item-danger')).toBe('heavy');
128
+ });
129
+ it('returns medium for primary action variants', () => {
130
+ expect(getHapticForButtonVariant('default')).toBe('medium');
131
+ expect(getHapticForButtonVariant('outline')).toBe('medium');
132
+ expect(getHapticForButtonVariant('landing')).toBe('medium');
133
+ });
134
+ it('returns light for secondary action variants', () => {
135
+ expect(getHapticForButtonVariant('alternative')).toBe('light');
136
+ expect(getHapticForButtonVariant('ghost')).toBe('light');
137
+ expect(getHapticForButtonVariant('landing-secondary')).toBe('light');
138
+ });
139
+ it('returns selection for toggle/nav variants', () => {
140
+ expect(getHapticForButtonVariant('toggle')).toBe('selection');
141
+ expect(getHapticForButtonVariant('nav')).toBe('selection');
142
+ });
143
+ it('returns null for variants that should not have haptic', () => {
144
+ expect(getHapticForButtonVariant('icon')).toBeNull();
145
+ expect(getHapticForButtonVariant('menu-item')).toBeNull();
146
+ expect(getHapticForButtonVariant('search-result')).toBeNull();
147
+ expect(getHapticForButtonVariant('link')).toBeNull();
148
+ });
149
+ it('returns light for unknown variants', () => {
150
+ expect(getHapticForButtonVariant('unknown-variant')).toBe('light');
151
+ });
152
+ });
153
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=imageOptimizer.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageOptimizer.spec.d.ts","sourceRoot":"","sources":["../../src/lib/utils/imageOptimizer.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,201 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { createImage, supportsWebP, optimizeImage } from './imageOptimizer';
3
+ describe('imageOptimizer', () => {
4
+ describe('createImage', () => {
5
+ it('creates an HTMLImageElement', () => {
6
+ // In jsdom, Image load events don't fire for data URLs reliably,
7
+ // so we test the function creates the right structure
8
+ const promise = createImage('https://example.com/test.png');
9
+ // The promise is pending, but we can verify it returns a promise
10
+ expect(promise).toBeInstanceOf(Promise);
11
+ });
12
+ it('does not set crossOrigin for blob URLs', () => {
13
+ const blob = new Blob([''], { type: 'image/png' });
14
+ const blobUrl = URL.createObjectURL(blob);
15
+ // Verify the logic by manually checking: blob URLs should not get crossOrigin
16
+ expect(blobUrl.startsWith('blob:')).toBe(true);
17
+ URL.revokeObjectURL(blobUrl);
18
+ });
19
+ });
20
+ describe('supportsWebP', () => {
21
+ it('returns true when canvas supports webp', () => {
22
+ const toDataURL = vi.fn().mockReturnValue('data:image/webp;base64,abc');
23
+ vi.spyOn(document, 'createElement').mockReturnValue({
24
+ width: 0,
25
+ height: 0,
26
+ toDataURL,
27
+ });
28
+ expect(supportsWebP()).toBe(true);
29
+ expect(toDataURL).toHaveBeenCalledWith('image/webp');
30
+ vi.restoreAllMocks();
31
+ });
32
+ it('returns false when canvas does not support webp', () => {
33
+ const toDataURL = vi.fn().mockReturnValue('data:image/png;base64,abc');
34
+ vi.spyOn(document, 'createElement').mockReturnValue({
35
+ width: 0,
36
+ height: 0,
37
+ toDataURL,
38
+ });
39
+ expect(supportsWebP()).toBe(false);
40
+ vi.restoreAllMocks();
41
+ });
42
+ });
43
+ describe('optimizeImage', () => {
44
+ let mockCanvas;
45
+ let mockCtx;
46
+ let originalImage;
47
+ beforeEach(() => {
48
+ mockCtx = {
49
+ imageSmoothingEnabled: false,
50
+ imageSmoothingQuality: '',
51
+ drawImage: vi.fn(),
52
+ };
53
+ mockCanvas = {
54
+ width: 0,
55
+ height: 0,
56
+ getContext: vi.fn().mockReturnValue(mockCtx),
57
+ toBlob: vi.fn(),
58
+ toDataURL: vi.fn().mockReturnValue('data:image/png;base64,abc'),
59
+ };
60
+ let callCount = 0;
61
+ vi.spyOn(document, 'createElement').mockImplementation((tag) => {
62
+ if (tag === 'canvas') {
63
+ callCount++;
64
+ // First canvas call is for optimizeImage, second is for supportsWebP
65
+ if (callCount === 1) {
66
+ return mockCanvas;
67
+ }
68
+ return {
69
+ width: 0,
70
+ height: 0,
71
+ toDataURL: vi.fn().mockReturnValue('data:image/png;base64,abc'),
72
+ };
73
+ }
74
+ return Object.getPrototypeOf(document).createElement.call(document, tag);
75
+ });
76
+ vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:test');
77
+ vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => { });
78
+ originalImage = globalThis.Image;
79
+ });
80
+ afterEach(() => {
81
+ vi.restoreAllMocks();
82
+ globalThis.Image = originalImage;
83
+ });
84
+ function mockImageConstructor(width, height, shouldFail = false) {
85
+ // @ts-expect-error mock constructor
86
+ globalThis.Image = class MockImage {
87
+ constructor() {
88
+ this.width = width;
89
+ this.height = height;
90
+ this.crossOrigin = '';
91
+ this.src = '';
92
+ this.listeners = {};
93
+ }
94
+ addEventListener(event, handler) {
95
+ if (!this.listeners[event])
96
+ this.listeners[event] = [];
97
+ this.listeners[event].push(handler);
98
+ if (event === 'load' && !shouldFail) {
99
+ setTimeout(() => handler(), 0);
100
+ }
101
+ if (event === 'error' && shouldFail) {
102
+ setTimeout(() => handler(new Error('load failed')), 0);
103
+ }
104
+ }
105
+ setAttribute(name, value) {
106
+ if (name === 'crossOrigin')
107
+ this.crossOrigin = value;
108
+ }
109
+ };
110
+ }
111
+ it('optimizes a landscape image with default options', async () => {
112
+ mockImageConstructor(2000, 1000);
113
+ mockCanvas.toBlob.mockImplementation((cb) => {
114
+ cb(new Blob(['optimized'], { type: 'image/jpeg' }));
115
+ });
116
+ const file = new File(['test'], 'photo.jpg', { type: 'image/jpeg' });
117
+ const result = await optimizeImage(file);
118
+ expect(result).toBeInstanceOf(File);
119
+ expect(result.name).toMatch(/photo\.(jpg|webp)$/);
120
+ expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:test');
121
+ expect(mockCanvas.width).toBe(1000);
122
+ expect(mockCanvas.height).toBe(500);
123
+ });
124
+ it('optimizes a portrait image', async () => {
125
+ mockImageConstructor(500, 2000);
126
+ mockCanvas.toBlob.mockImplementation((cb) => {
127
+ cb(new Blob(['optimized'], { type: 'image/jpeg' }));
128
+ });
129
+ const file = new File(['test'], 'photo.png', { type: 'image/png' });
130
+ const result = await optimizeImage(file);
131
+ expect(result).toBeInstanceOf(File);
132
+ expect(mockCanvas.height).toBe(1000);
133
+ expect(mockCanvas.width).toBe(250);
134
+ });
135
+ it('does not upscale small images', async () => {
136
+ mockImageConstructor(200, 150);
137
+ mockCanvas.toBlob.mockImplementation((cb) => {
138
+ cb(new Blob(['optimized'], { type: 'image/jpeg' }));
139
+ });
140
+ const file = new File(['test'], 'small.jpg', { type: 'image/jpeg' });
141
+ await optimizeImage(file);
142
+ expect(mockCanvas.width).toBe(200);
143
+ expect(mockCanvas.height).toBe(150);
144
+ });
145
+ it('handles forceSquare option', async () => {
146
+ mockImageConstructor(800, 600);
147
+ mockCanvas.toBlob.mockImplementation((cb) => {
148
+ cb(new Blob(['optimized'], { type: 'image/jpeg' }));
149
+ });
150
+ const file = new File(['test'], 'photo.jpg', { type: 'image/jpeg' });
151
+ await optimizeImage(file, { forceSquare: true, maxSize: 500 });
152
+ expect(mockCanvas.width).toBe(500);
153
+ expect(mockCanvas.height).toBe(500);
154
+ expect(mockCtx.drawImage).toHaveBeenCalledWith(expect.anything(), 100, 0, 600, 600, 0, 0, 500, 500);
155
+ });
156
+ it('uses custom quality when provided', async () => {
157
+ mockImageConstructor(2000, 1000);
158
+ mockCanvas.toBlob.mockImplementation((cb) => {
159
+ cb(new Blob(['optimized'], { type: 'image/jpeg' }));
160
+ });
161
+ const file = new File(['test'], 'photo.jpg', { type: 'image/jpeg' });
162
+ await optimizeImage(file, { quality: 0.5 });
163
+ expect(mockCanvas.toBlob).toHaveBeenCalledWith(expect.any(Function), expect.any(String), 0.5);
164
+ });
165
+ it('uses custom maxSize', async () => {
166
+ mockImageConstructor(2000, 1000);
167
+ mockCanvas.toBlob.mockImplementation((cb) => {
168
+ cb(new Blob(['optimized'], { type: 'image/jpeg' }));
169
+ });
170
+ const file = new File(['test'], 'photo.jpg', { type: 'image/jpeg' });
171
+ await optimizeImage(file, { maxSize: 500 });
172
+ expect(mockCanvas.width).toBe(500);
173
+ expect(mockCanvas.height).toBe(250);
174
+ });
175
+ it('revokes blob URL even on error', async () => {
176
+ mockImageConstructor(0, 0, true);
177
+ const file = new File(['test'], 'bad.jpg', { type: 'image/jpeg' });
178
+ await expect(optimizeImage(file)).rejects.toBeDefined();
179
+ expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:test');
180
+ });
181
+ it('replaces file extension in output filename', async () => {
182
+ mockImageConstructor(200, 100);
183
+ mockCanvas.toBlob.mockImplementation((cb) => {
184
+ cb(new Blob(['optimized'], { type: 'image/jpeg' }));
185
+ });
186
+ const file = new File(['test'], 'my-photo.png', { type: 'image/png' });
187
+ const result = await optimizeImage(file);
188
+ expect(result.name).toMatch(/my-photo\.(jpg|webp)$/);
189
+ });
190
+ it('sets imageSmoothingEnabled and quality', async () => {
191
+ mockImageConstructor(200, 100);
192
+ mockCanvas.toBlob.mockImplementation((cb) => {
193
+ cb(new Blob(['optimized'], { type: 'image/jpeg' }));
194
+ });
195
+ const file = new File(['test'], 'photo.jpg', { type: 'image/jpeg' });
196
+ await optimizeImage(file);
197
+ expect(mockCtx.imageSmoothingEnabled).toBe(true);
198
+ expect(mockCtx.imageSmoothingQuality).toBe('high');
199
+ });
200
+ });
201
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=logger.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.spec.d.ts","sourceRoot":"","sources":["../../src/lib/utils/logger.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,95 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { logger, configureLogger } from './logger';
3
+ describe('logger', () => {
4
+ beforeEach(() => {
5
+ vi.spyOn(console, 'log').mockImplementation(() => { });
6
+ vi.spyOn(console, 'info').mockImplementation(() => { });
7
+ vi.spyOn(console, 'warn').mockImplementation(() => { });
8
+ vi.spyOn(console, 'error').mockImplementation(() => { });
9
+ // Reset to default enabled state
10
+ configureLogger({ enabled: true, level: 'debug', prefix: '[MicDrop]' });
11
+ });
12
+ afterEach(() => {
13
+ vi.restoreAllMocks();
14
+ });
15
+ describe('log methods', () => {
16
+ it('debug logs with console.log', () => {
17
+ logger.debug('test message');
18
+ expect(console.log).toHaveBeenCalledWith('[MicDrop] [DEBUG] test message');
19
+ });
20
+ it('info logs with console.info', () => {
21
+ logger.info('test message');
22
+ expect(console.info).toHaveBeenCalledWith('[MicDrop] [INFO] test message');
23
+ });
24
+ it('warn logs with console.warn', () => {
25
+ logger.warn('test message');
26
+ expect(console.warn).toHaveBeenCalledWith('[MicDrop] [WARN] test message');
27
+ });
28
+ it('error logs with console.error', () => {
29
+ logger.error('test message');
30
+ expect(console.error).toHaveBeenCalledWith('[MicDrop] [ERROR] test message');
31
+ });
32
+ it('passes extra arguments through', () => {
33
+ const data = { foo: 'bar' };
34
+ logger.debug('message', data);
35
+ expect(console.log).toHaveBeenCalledWith('[MicDrop] [DEBUG] message', data);
36
+ });
37
+ it('passes multiple extra arguments', () => {
38
+ logger.info('msg', 1, 'two', { three: 3 });
39
+ expect(console.info).toHaveBeenCalledWith('[MicDrop] [INFO] msg', 1, 'two', { three: 3 });
40
+ });
41
+ });
42
+ describe('configureLogger', () => {
43
+ it('can disable logging', () => {
44
+ configureLogger({ enabled: false });
45
+ logger.debug('should not appear');
46
+ logger.info('should not appear');
47
+ logger.warn('should not appear');
48
+ logger.error('should not appear');
49
+ expect(console.log).not.toHaveBeenCalled();
50
+ expect(console.info).not.toHaveBeenCalled();
51
+ expect(console.warn).not.toHaveBeenCalled();
52
+ expect(console.error).not.toHaveBeenCalled();
53
+ });
54
+ it('can change prefix', () => {
55
+ configureLogger({ prefix: '[Custom]' });
56
+ logger.debug('test');
57
+ expect(console.log).toHaveBeenCalledWith('[Custom] [DEBUG] test');
58
+ });
59
+ it('can set log level to filter lower levels', () => {
60
+ configureLogger({ level: 'warn' });
61
+ logger.debug('debug msg');
62
+ logger.info('info msg');
63
+ logger.warn('warn msg');
64
+ logger.error('error msg');
65
+ expect(console.log).not.toHaveBeenCalled();
66
+ expect(console.info).not.toHaveBeenCalled();
67
+ expect(console.warn).toHaveBeenCalledTimes(1);
68
+ expect(console.error).toHaveBeenCalledTimes(1);
69
+ });
70
+ it('level info filters out debug', () => {
71
+ configureLogger({ level: 'info' });
72
+ logger.debug('debug msg');
73
+ logger.info('info msg');
74
+ expect(console.log).not.toHaveBeenCalled();
75
+ expect(console.info).toHaveBeenCalledTimes(1);
76
+ });
77
+ it('level error filters out everything except error', () => {
78
+ configureLogger({ level: 'error' });
79
+ logger.debug('d');
80
+ logger.info('i');
81
+ logger.warn('w');
82
+ logger.error('e');
83
+ expect(console.log).not.toHaveBeenCalled();
84
+ expect(console.info).not.toHaveBeenCalled();
85
+ expect(console.warn).not.toHaveBeenCalled();
86
+ expect(console.error).toHaveBeenCalledTimes(1);
87
+ });
88
+ it('partial config merges with existing', () => {
89
+ configureLogger({ prefix: '[Test]' });
90
+ configureLogger({ level: 'warn' });
91
+ logger.warn('test');
92
+ expect(console.warn).toHaveBeenCalledWith('[Test] [WARN] test');
93
+ });
94
+ });
95
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getmicdrop/svelte-components",
3
- "version": "5.17.1",
3
+ "version": "5.17.4",
4
4
  "description": "Shared component library for Micdrop applications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -248,7 +248,6 @@
248
248
  "@tailwindcss/vite": "^4.0.0",
249
249
  "@testing-library/jest-dom": "^6.4.6",
250
250
  "@testing-library/svelte": "^5.2.0",
251
- "@types/cookie": "^1.0.0",
252
251
  "@types/node": "^20.14.12",
253
252
  "@typescript-eslint/parser": "^8.52.0",
254
253
  "@vitest/browser": "^4.0.17",