@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.
- package/dist/calendar/Calendar/MiniMonthCalendar.svelte +5 -7
- package/dist/calendar/Calendar/MiniMonthCalendar.svelte.d.ts.map +1 -1
- package/dist/calendar/MonthSwitcher/MonthSwitcher.svelte +2 -3
- package/dist/calendar/MonthSwitcher/MonthSwitcher.svelte.d.ts.map +1 -1
- package/dist/calendar/PublicCard/PublicCard.svelte +23 -14
- package/dist/calendar/PublicCard/PublicCard.svelte.d.ts.map +1 -1
- package/dist/calendar/ShowCard/ShowCard.spec.js +1 -7
- package/dist/calendar/ShowCard/ShowCard.svelte +10 -1
- package/dist/calendar/ShowCard/ShowCard.svelte.d.ts.map +1 -1
- package/dist/calendar/ShowTimeCard/ShowTimeCard.svelte +11 -0
- package/dist/calendar/ShowTimeCard/ShowTimeCard.svelte.d.ts +2 -0
- package/dist/calendar/ShowTimeCard/ShowTimeCard.svelte.d.ts.map +1 -1
- package/dist/components/Heading.spec.d.ts +2 -0
- package/dist/components/Heading.spec.d.ts.map +1 -0
- package/dist/components/Heading.spec.js +89 -0
- package/dist/components/Layout/__tests__/AppShell.test.js +140 -0
- package/dist/components/Text.spec.d.ts +2 -0
- package/dist/components/Text.spec.d.ts.map +1 -0
- package/dist/components/Text.spec.js +89 -0
- package/dist/config.d.ts +102 -0
- package/dist/config.js +147 -1
- package/dist/datetime/README.md +323 -0
- package/dist/forms/createFormStore.svelte.spec.d.ts +2 -0
- package/dist/forms/createFormStore.svelte.spec.d.ts.map +1 -0
- package/dist/forms/createFormStore.svelte.spec.js +387 -0
- package/dist/messages.d.ts +43 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +57 -0
- package/dist/patterns/chat/ChatActivityNotice.spec.d.ts +2 -0
- package/dist/patterns/chat/ChatActivityNotice.spec.d.ts.map +1 -0
- package/dist/patterns/chat/ChatActivityNotice.spec.js +59 -0
- package/dist/patterns/chat/ChatBubble.spec.d.ts +2 -0
- package/dist/patterns/chat/ChatBubble.spec.d.ts.map +1 -0
- package/dist/patterns/chat/ChatBubble.spec.js +91 -0
- package/dist/patterns/chat/ChatContainer.spec.d.ts +2 -0
- package/dist/patterns/chat/ChatContainer.spec.d.ts.map +1 -0
- package/dist/patterns/chat/ChatContainer.spec.js +30 -0
- package/dist/patterns/chat/ChatDateDivider.spec.d.ts +2 -0
- package/dist/patterns/chat/ChatDateDivider.spec.d.ts.map +1 -0
- package/dist/patterns/chat/ChatDateDivider.spec.js +30 -0
- package/dist/patterns/chat/ChatInvitationBubble.spec.d.ts +2 -0
- package/dist/patterns/chat/ChatInvitationBubble.spec.d.ts.map +1 -0
- package/dist/patterns/chat/ChatInvitationBubble.spec.js +46 -0
- package/dist/patterns/chat/ChatInvitationNotice.spec.d.ts +2 -0
- package/dist/patterns/chat/ChatInvitationNotice.spec.d.ts.map +1 -0
- package/dist/patterns/chat/ChatInvitationNotice.spec.js +32 -0
- package/dist/patterns/chat/ChatMessageGroup.spec.d.ts +2 -0
- package/dist/patterns/chat/ChatMessageGroup.spec.d.ts.map +1 -0
- package/dist/patterns/chat/ChatMessageGroup.spec.js +58 -0
- package/dist/patterns/chat/ChatSlotUpdate.spec.d.ts +2 -0
- package/dist/patterns/chat/ChatSlotUpdate.spec.d.ts.map +1 -0
- package/dist/patterns/chat/ChatSlotUpdate.spec.js +65 -0
- package/dist/patterns/chat/ChatStatusBadge.spec.d.ts +2 -0
- package/dist/patterns/chat/ChatStatusBadge.spec.d.ts.map +1 -0
- package/dist/patterns/chat/ChatStatusBadge.spec.js +79 -0
- package/dist/patterns/chat/ChatStatusTransition.spec.d.ts +2 -0
- package/dist/patterns/chat/ChatStatusTransition.spec.d.ts.map +1 -0
- package/dist/patterns/chat/ChatStatusTransition.spec.js +81 -0
- package/dist/patterns/chat/ChatTextBubble.spec.d.ts +2 -0
- package/dist/patterns/chat/ChatTextBubble.spec.d.ts.map +1 -0
- package/dist/patterns/chat/ChatTextBubble.spec.js +35 -0
- package/dist/patterns/data/DataTable.spec.js +61 -0
- package/dist/patterns/forms/FormGrid.spec.js +34 -0
- package/dist/patterns/layout/Sidebar.spec.js +240 -1
- package/dist/patterns/layout/SidebarTestWrapper.svelte +34 -0
- package/dist/patterns/layout/SidebarTestWrapper.svelte.d.ts +23 -0
- package/dist/patterns/layout/SidebarTestWrapper.svelte.d.ts.map +1 -0
- package/dist/patterns/navigation/Header.spec.js +123 -0
- package/dist/primitives/Accordion/Accordion.spec.js +112 -2
- package/dist/primitives/Accordion/AccordionToggleWrapper.test.svelte +28 -0
- package/dist/primitives/Accordion/AccordionToggleWrapper.test.svelte.d.ts +7 -0
- package/dist/primitives/Accordion/AccordionToggleWrapper.test.svelte.d.ts.map +1 -0
- package/dist/primitives/Avatar/Avatar.spec.js +23 -0
- package/dist/primitives/BottomSheet/BottomSheet.spec.js +102 -0
- package/dist/primitives/BottomSheet/BottomSheetWithActions.test.svelte +20 -0
- package/dist/primitives/BottomSheet/BottomSheetWithActions.test.svelte.d.ts +10 -0
- package/dist/primitives/BottomSheet/BottomSheetWithActions.test.svelte.d.ts.map +1 -0
- package/dist/primitives/Button/ButtonGroup.spec.d.ts +2 -0
- package/dist/primitives/Button/ButtonGroup.spec.d.ts.map +1 -0
- package/dist/primitives/Button/ButtonGroup.spec.js +44 -0
- package/dist/primitives/Checkbox/Checkbox.spec.js +32 -0
- package/dist/primitives/Drawer/Drawer.spec.js +437 -0
- package/dist/primitives/Drawer/DrawerTestWrapper.svelte +86 -0
- package/dist/primitives/Drawer/DrawerTestWrapper.svelte.d.ts +26 -0
- package/dist/primitives/Drawer/DrawerTestWrapper.svelte.d.ts.map +1 -0
- package/dist/primitives/Dropdown/Dropdown.spec.js +116 -0
- package/dist/primitives/Dropdown/DropdownDivider.spec.d.ts +2 -0
- package/dist/primitives/Dropdown/DropdownDivider.spec.d.ts.map +1 -0
- package/dist/primitives/Dropdown/DropdownDivider.spec.js +30 -0
- package/dist/primitives/Dropdown/DropdownItem.spec.js +155 -1
- package/dist/primitives/Dropdown/DropdownItemTestWrapper.svelte +43 -0
- package/dist/primitives/Dropdown/DropdownItemTestWrapper.svelte.d.ts +17 -0
- package/dist/primitives/Dropdown/DropdownItemTestWrapper.svelte.d.ts.map +1 -0
- package/dist/primitives/Helper/Helper.spec.d.ts +2 -0
- package/dist/primitives/Helper/Helper.spec.d.ts.map +1 -0
- package/dist/primitives/Helper/Helper.spec.js +57 -0
- package/dist/primitives/Input/Input.spec.js +664 -0
- package/dist/primitives/Input/Input.svelte +18 -10
- package/dist/primitives/Input/Input.svelte.d.ts.map +1 -1
- package/dist/primitives/Input/Select.spec.js +414 -0
- package/dist/primitives/Label/Label.spec.js +9 -0
- package/dist/primitives/LandingButton/LandingButton.spec.d.ts +2 -0
- package/dist/primitives/LandingButton/LandingButton.spec.d.ts.map +1 -0
- package/dist/primitives/LandingButton/LandingButton.spec.js +61 -0
- package/dist/primitives/MenuItem/MenuItem.spec.d.ts +2 -0
- package/dist/primitives/MenuItem/MenuItem.spec.d.ts.map +1 -0
- package/dist/primitives/MenuItem/MenuItem.spec.js +130 -0
- package/dist/primitives/Modal/Modal.spec.js +215 -0
- package/dist/primitives/NavItem/NavItem.spec.d.ts +2 -0
- package/dist/primitives/NavItem/NavItem.spec.d.ts.map +1 -0
- package/dist/primitives/NavItem/NavItem.spec.js +97 -0
- package/dist/primitives/SearchResultItem/SearchResultItem.spec.d.ts +2 -0
- package/dist/primitives/SearchResultItem/SearchResultItem.spec.d.ts.map +1 -0
- package/dist/primitives/SearchResultItem/SearchResultItem.spec.js +78 -0
- package/dist/primitives/SidebarToggle/SidebarToggle.spec.d.ts +2 -0
- package/dist/primitives/SidebarToggle/SidebarToggle.spec.d.ts.map +1 -0
- package/dist/primitives/SidebarToggle/SidebarToggle.spec.js +61 -0
- package/dist/primitives/Spinner/Spinner.spec.js +13 -0
- package/dist/primitives/Toggle.spec.js +75 -0
- package/dist/primitives/ToggleTestWrapper.svelte +30 -0
- package/dist/primitives/ToggleTestWrapper.svelte.d.ts +29 -0
- package/dist/primitives/ToggleTestWrapper.svelte.d.ts.map +1 -0
- package/dist/primitives/Tooltip/Tooltip.spec.d.ts +2 -0
- package/dist/primitives/Tooltip/Tooltip.spec.d.ts.map +1 -0
- package/dist/primitives/Tooltip/Tooltip.spec.js +126 -0
- package/dist/recipes/inputs/Search.spec.js +66 -2
- package/dist/recipes/modals/ConfirmationModal.spec.js +190 -0
- package/dist/schemas/__tests__/auth.test.d.ts +2 -0
- package/dist/schemas/__tests__/auth.test.d.ts.map +1 -0
- package/dist/schemas/__tests__/auth.test.js +210 -0
- package/dist/schemas/__tests__/common.test.d.ts +2 -0
- package/dist/schemas/__tests__/common.test.d.ts.map +1 -0
- package/dist/schemas/__tests__/common.test.js +340 -0
- package/dist/schemas/__tests__/domain.test.d.ts +2 -0
- package/dist/schemas/__tests__/domain.test.d.ts.map +1 -0
- package/dist/schemas/__tests__/domain.test.js +293 -0
- package/dist/schemas/__tests__/order.test.d.ts +2 -0
- package/dist/schemas/__tests__/order.test.d.ts.map +1 -0
- package/dist/schemas/__tests__/order.test.js +349 -0
- package/dist/schemas/__tests__/user.test.d.ts +2 -0
- package/dist/schemas/__tests__/user.test.d.ts.map +1 -0
- package/dist/schemas/__tests__/user.test.js +325 -0
- package/dist/schemas/auth.d.ts +41 -0
- package/dist/schemas/auth.d.ts.map +1 -0
- package/dist/schemas/auth.js +69 -0
- package/dist/schemas/common.d.ts +43 -0
- package/dist/schemas/common.d.ts.map +1 -0
- package/dist/schemas/common.js +157 -0
- package/dist/schemas/event.d.ts +82 -0
- package/dist/schemas/event.d.ts.map +1 -0
- package/dist/schemas/event.js +58 -0
- package/dist/schemas/index.d.ts +10 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +9 -0
- package/dist/schemas/order.d.ts +111 -0
- package/dist/schemas/order.d.ts.map +1 -0
- package/dist/schemas/order.js +73 -0
- package/dist/schemas/performer.d.ts +133 -0
- package/dist/schemas/performer.d.ts.map +1 -0
- package/dist/schemas/performer.js +73 -0
- package/dist/schemas/promo.d.ts +87 -0
- package/dist/schemas/promo.d.ts.map +1 -0
- package/dist/schemas/promo.js +98 -0
- package/dist/schemas/ticket.d.ts +104 -0
- package/dist/schemas/ticket.d.ts.map +1 -0
- package/dist/schemas/ticket.js +82 -0
- package/dist/schemas/user.d.ts +92 -0
- package/dist/schemas/user.d.ts.map +1 -0
- package/dist/schemas/user.js +53 -0
- package/dist/schemas/venue.d.ts +95 -0
- package/dist/schemas/venue.d.ts.map +1 -0
- package/dist/schemas/venue.js +52 -0
- package/dist/stores/auth.svelte.spec.d.ts +2 -0
- package/dist/stores/auth.svelte.spec.d.ts.map +1 -0
- package/dist/stores/auth.svelte.spec.js +112 -0
- package/dist/stores/formDataStore.svelte.spec.d.ts +2 -0
- package/dist/stores/formDataStore.svelte.spec.d.ts.map +1 -0
- package/dist/stores/formDataStore.svelte.spec.js +150 -0
- package/dist/stores/formSave.svelte.spec.d.ts +2 -0
- package/dist/stores/formSave.svelte.spec.d.ts.map +1 -0
- package/dist/stores/formSave.svelte.spec.js +196 -0
- package/dist/stores/navigation.spec.d.ts +2 -0
- package/dist/stores/navigation.spec.d.ts.map +1 -0
- package/dist/stores/navigation.spec.js +53 -0
- package/dist/telemetry.spec.js +5 -0
- package/dist/tokens/__tests__/sizing.test.js +2 -2
- package/dist/tokens/sizing.d.ts +5 -0
- package/dist/tokens/sizing.d.ts.map +1 -1
- package/dist/tokens/sizing.js +6 -0
- package/dist/utils/haptic.spec.d.ts +2 -0
- package/dist/utils/haptic.spec.d.ts.map +1 -0
- package/dist/utils/haptic.spec.js +153 -0
- package/dist/utils/imageOptimizer.spec.d.ts +2 -0
- package/dist/utils/imageOptimizer.spec.d.ts.map +1 -0
- package/dist/utils/imageOptimizer.spec.js +201 -0
- package/dist/utils/logger.spec.d.ts +2 -0
- package/dist/utils/logger.spec.d.ts.map +1 -0
- package/dist/utils/logger.spec.js +95 -0
- 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 @@
|
|
|
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 @@
|
|
|
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.
|
|
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",
|