@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,387 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { createFormStore } from './createFormStore.svelte';
|
|
4
|
+
const testSchema = z.object({
|
|
5
|
+
name: z.string().min(1, 'Name is required'),
|
|
6
|
+
email: z.string().email('Invalid email'),
|
|
7
|
+
age: z.number().min(0, 'Age must be positive').optional(),
|
|
8
|
+
});
|
|
9
|
+
const defaultData = {
|
|
10
|
+
name: '',
|
|
11
|
+
email: '',
|
|
12
|
+
age: undefined,
|
|
13
|
+
};
|
|
14
|
+
const validData = {
|
|
15
|
+
name: 'John',
|
|
16
|
+
email: 'john@example.com',
|
|
17
|
+
age: 30,
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Helper to create a form store within an effect root context.
|
|
21
|
+
* Required because createFormStore uses $effect internally.
|
|
22
|
+
*/
|
|
23
|
+
function createTestStore(schema, initialData, options = {}) {
|
|
24
|
+
let store;
|
|
25
|
+
const cleanup = $effect.root(() => {
|
|
26
|
+
store = createFormStore(schema, initialData, options);
|
|
27
|
+
});
|
|
28
|
+
return { store: store, cleanup };
|
|
29
|
+
}
|
|
30
|
+
describe('createFormStore', () => {
|
|
31
|
+
let cleanups = [];
|
|
32
|
+
function makeStore(data = defaultData, options = {}) {
|
|
33
|
+
const { store, cleanup } = createTestStore(testSchema, data, options);
|
|
34
|
+
cleanups.push(cleanup);
|
|
35
|
+
return store;
|
|
36
|
+
}
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
cleanups.forEach(fn => fn());
|
|
39
|
+
cleanups = [];
|
|
40
|
+
});
|
|
41
|
+
describe('initial state', () => {
|
|
42
|
+
it('creates a store with default data', () => {
|
|
43
|
+
const store = makeStore();
|
|
44
|
+
expect(store.data).toEqual(defaultData);
|
|
45
|
+
});
|
|
46
|
+
it('starts with null originalData', () => {
|
|
47
|
+
const store = makeStore();
|
|
48
|
+
expect(store.originalData).toBeNull();
|
|
49
|
+
});
|
|
50
|
+
it('starts with empty errors', () => {
|
|
51
|
+
const store = makeStore();
|
|
52
|
+
expect(store.errors).toEqual({});
|
|
53
|
+
});
|
|
54
|
+
it('starts with all UI states as false', () => {
|
|
55
|
+
const store = makeStore();
|
|
56
|
+
expect(store.isLoading).toBe(false);
|
|
57
|
+
expect(store.isSaving).toBe(false);
|
|
58
|
+
expect(store.isSaved).toBe(false);
|
|
59
|
+
expect(store.showErrors).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
it('isDirty is false initially (edit mode)', () => {
|
|
62
|
+
const store = makeStore();
|
|
63
|
+
expect(store.isDirty).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
it('isValid reflects schema validation', () => {
|
|
66
|
+
const store = makeStore();
|
|
67
|
+
expect(store.isValid).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
it('isValid is true for valid data', () => {
|
|
70
|
+
const store = makeStore(validData);
|
|
71
|
+
expect(store.isValid).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
it('canSave is false when not dirty', () => {
|
|
74
|
+
const store = makeStore(validData);
|
|
75
|
+
expect(store.canSave).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe('data manipulation', () => {
|
|
79
|
+
it('can set data', () => {
|
|
80
|
+
const store = makeStore();
|
|
81
|
+
store.data = validData;
|
|
82
|
+
expect(store.data).toEqual(validData);
|
|
83
|
+
});
|
|
84
|
+
it('can set originalData', () => {
|
|
85
|
+
const store = makeStore();
|
|
86
|
+
store.originalData = validData;
|
|
87
|
+
expect(store.originalData).toEqual(validData);
|
|
88
|
+
});
|
|
89
|
+
it('can set errors', () => {
|
|
90
|
+
const store = makeStore();
|
|
91
|
+
store.errors = { name: 'Required' };
|
|
92
|
+
expect(store.errors).toEqual({ name: 'Required' });
|
|
93
|
+
});
|
|
94
|
+
it('can set UI state flags', () => {
|
|
95
|
+
const store = makeStore();
|
|
96
|
+
store.isLoading = true;
|
|
97
|
+
expect(store.isLoading).toBe(true);
|
|
98
|
+
store.isSaving = true;
|
|
99
|
+
expect(store.isSaving).toBe(true);
|
|
100
|
+
store.isSaved = true;
|
|
101
|
+
expect(store.isSaved).toBe(true);
|
|
102
|
+
store.showErrors = true;
|
|
103
|
+
expect(store.showErrors).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('setOriginal', () => {
|
|
107
|
+
it('sets both original and current data', () => {
|
|
108
|
+
const store = makeStore();
|
|
109
|
+
store.setOriginal(validData);
|
|
110
|
+
expect(store.originalData).toEqual(validData);
|
|
111
|
+
expect(store.data).toEqual(validData);
|
|
112
|
+
});
|
|
113
|
+
it('clears errors and showErrors', () => {
|
|
114
|
+
const store = makeStore();
|
|
115
|
+
store.errors = { name: 'err' };
|
|
116
|
+
store.showErrors = true;
|
|
117
|
+
store.setOriginal(validData);
|
|
118
|
+
expect(store.errors).toEqual({});
|
|
119
|
+
expect(store.showErrors).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
it('resets isSaved', () => {
|
|
122
|
+
const store = makeStore();
|
|
123
|
+
store.isSaved = true;
|
|
124
|
+
store.setOriginal(validData);
|
|
125
|
+
expect(store.isSaved).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
describe('isDirty', () => {
|
|
129
|
+
it('is false when data matches original (edit mode)', () => {
|
|
130
|
+
const store = makeStore();
|
|
131
|
+
store.setOriginal(validData);
|
|
132
|
+
expect(store.isDirty).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
it('is true when data differs from original (edit mode)', () => {
|
|
135
|
+
const store = makeStore();
|
|
136
|
+
store.setOriginal(validData);
|
|
137
|
+
store.data = { ...validData, name: 'Jane' };
|
|
138
|
+
expect(store.isDirty).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
it('is false when no originalData set (edit mode)', () => {
|
|
141
|
+
const store = makeStore();
|
|
142
|
+
store.data = validData;
|
|
143
|
+
expect(store.isDirty).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
describe('isDirty (create mode)', () => {
|
|
147
|
+
it('is false when all fields empty', () => {
|
|
148
|
+
const store = makeStore(defaultData, { mode: 'create' });
|
|
149
|
+
expect(store.isDirty).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
it('is true when string field has value', () => {
|
|
152
|
+
const store = makeStore(defaultData, { mode: 'create' });
|
|
153
|
+
store.data = { ...defaultData, name: 'test' };
|
|
154
|
+
expect(store.isDirty).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
it('is true when number field has value', () => {
|
|
157
|
+
const store = makeStore(defaultData, { mode: 'create' });
|
|
158
|
+
store.data = { ...defaultData, age: 25 };
|
|
159
|
+
expect(store.isDirty).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
it('handles array fields', () => {
|
|
162
|
+
const arraySchema = z.object({ tags: z.array(z.string()), name: z.string() });
|
|
163
|
+
const { store, cleanup } = createTestStore(arraySchema, { tags: [], name: '' }, { mode: 'create' });
|
|
164
|
+
cleanups.push(cleanup);
|
|
165
|
+
expect(store.isDirty).toBe(false);
|
|
166
|
+
store.data = { tags: ['test'], name: '' };
|
|
167
|
+
expect(store.isDirty).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
it('handles boolean fields', () => {
|
|
170
|
+
const boolSchema = z.object({ active: z.boolean(), name: z.string() });
|
|
171
|
+
const { store, cleanup } = createTestStore(boolSchema, { active: false, name: '' }, { mode: 'create' });
|
|
172
|
+
cleanups.push(cleanup);
|
|
173
|
+
expect(store.isDirty).toBe(false);
|
|
174
|
+
store.data = { active: true, name: '' };
|
|
175
|
+
expect(store.isDirty).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
it('handles null values', () => {
|
|
178
|
+
const nullSchema = z.object({ value: z.string().nullable() });
|
|
179
|
+
const { store, cleanup } = createTestStore(nullSchema, { value: null }, { mode: 'create' });
|
|
180
|
+
cleanups.push(cleanup);
|
|
181
|
+
expect(store.isDirty).toBe(false);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
describe('validate', () => {
|
|
185
|
+
it('returns false and sets errors for invalid data', () => {
|
|
186
|
+
const store = makeStore();
|
|
187
|
+
const result = store.validate();
|
|
188
|
+
expect(result).toBe(false);
|
|
189
|
+
expect(store.showErrors).toBe(true);
|
|
190
|
+
expect(store.errors.name).toBeDefined();
|
|
191
|
+
expect(store.errors.email).toBeDefined();
|
|
192
|
+
});
|
|
193
|
+
it('returns true and clears errors for valid data', () => {
|
|
194
|
+
const store = makeStore(validData);
|
|
195
|
+
const result = store.validate();
|
|
196
|
+
expect(result).toBe(true);
|
|
197
|
+
expect(store.errors).toEqual({});
|
|
198
|
+
});
|
|
199
|
+
it('only keeps first error per field', () => {
|
|
200
|
+
const store = makeStore({ name: '', email: 'bad', age: undefined });
|
|
201
|
+
store.validate();
|
|
202
|
+
expect(typeof store.errors.name).toBe('string');
|
|
203
|
+
expect(typeof store.errors.email).toBe('string');
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
describe('validateField', () => {
|
|
207
|
+
it('returns undefined for valid field', () => {
|
|
208
|
+
const store = makeStore(validData);
|
|
209
|
+
expect(store.validateField('name')).toBeUndefined();
|
|
210
|
+
});
|
|
211
|
+
it('returns error message for invalid field', () => {
|
|
212
|
+
const store = makeStore();
|
|
213
|
+
expect(store.validateField('name')).toBeDefined();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
describe('fieldError', () => {
|
|
217
|
+
it('returns undefined when showErrors is false', () => {
|
|
218
|
+
const store = makeStore();
|
|
219
|
+
store.errors = { name: 'Required' };
|
|
220
|
+
expect(store.fieldError('name')).toBeUndefined();
|
|
221
|
+
});
|
|
222
|
+
it('returns error when showErrors is true', () => {
|
|
223
|
+
const store = makeStore();
|
|
224
|
+
store.errors = { name: 'Required' };
|
|
225
|
+
store.showErrors = true;
|
|
226
|
+
expect(store.fieldError('name')).toBe('Required');
|
|
227
|
+
});
|
|
228
|
+
it('returns undefined for fields without errors', () => {
|
|
229
|
+
const store = makeStore();
|
|
230
|
+
store.showErrors = true;
|
|
231
|
+
expect(store.fieldError('age')).toBeUndefined();
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
describe('sections', () => {
|
|
235
|
+
const sections = {
|
|
236
|
+
basics: ['name', 'email'],
|
|
237
|
+
details: ['age'],
|
|
238
|
+
};
|
|
239
|
+
it('isSectionValid returns true for unknown section', () => {
|
|
240
|
+
const store = makeStore(defaultData, { sections });
|
|
241
|
+
expect(store.isSectionValid('nonexistent')).toBe(true);
|
|
242
|
+
});
|
|
243
|
+
it('isSectionValid returns false when section has errors', () => {
|
|
244
|
+
const store = makeStore(defaultData, { sections });
|
|
245
|
+
expect(store.isSectionValid('basics')).toBe(false);
|
|
246
|
+
});
|
|
247
|
+
it('isSectionValid returns true when section fields are valid', () => {
|
|
248
|
+
const store = makeStore({ name: 'John', email: 'john@test.com', age: undefined }, { sections });
|
|
249
|
+
expect(store.isSectionValid('basics')).toBe(true);
|
|
250
|
+
});
|
|
251
|
+
it('isSectionDirty returns false when no original data', () => {
|
|
252
|
+
const store = makeStore(defaultData, { sections });
|
|
253
|
+
expect(store.isSectionDirty('basics')).toBe(false);
|
|
254
|
+
});
|
|
255
|
+
it('isSectionDirty returns false when section has no changes', () => {
|
|
256
|
+
const store = makeStore(defaultData, { sections });
|
|
257
|
+
store.setOriginal(validData);
|
|
258
|
+
expect(store.isSectionDirty('basics')).toBe(false);
|
|
259
|
+
});
|
|
260
|
+
it('isSectionDirty returns true when section field changed', () => {
|
|
261
|
+
const store = makeStore(defaultData, { sections });
|
|
262
|
+
store.setOriginal(validData);
|
|
263
|
+
store.data = { ...validData, name: 'Changed' };
|
|
264
|
+
expect(store.isSectionDirty('basics')).toBe(true);
|
|
265
|
+
});
|
|
266
|
+
it('isSectionDirty returns false for unchanged section', () => {
|
|
267
|
+
const store = makeStore(defaultData, { sections });
|
|
268
|
+
store.setOriginal(validData);
|
|
269
|
+
store.data = { ...validData, name: 'Changed' };
|
|
270
|
+
expect(store.isSectionDirty('details')).toBe(false);
|
|
271
|
+
});
|
|
272
|
+
it('isSectionDirty returns false for unknown section', () => {
|
|
273
|
+
const store = makeStore(defaultData, { sections });
|
|
274
|
+
expect(store.isSectionDirty('nonexistent')).toBe(false);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
describe('reset', () => {
|
|
278
|
+
it('restores data to initial values', () => {
|
|
279
|
+
const store = makeStore();
|
|
280
|
+
store.data = validData;
|
|
281
|
+
store.reset();
|
|
282
|
+
expect(store.data).toEqual(defaultData);
|
|
283
|
+
});
|
|
284
|
+
it('clears all state', () => {
|
|
285
|
+
const store = makeStore();
|
|
286
|
+
store.setOriginal(validData);
|
|
287
|
+
store.isLoading = true;
|
|
288
|
+
store.isSaving = true;
|
|
289
|
+
store.isSaved = true;
|
|
290
|
+
store.showErrors = true;
|
|
291
|
+
store.errors = { name: 'err' };
|
|
292
|
+
store.reset();
|
|
293
|
+
expect(store.originalData).toBeNull();
|
|
294
|
+
expect(store.errors).toEqual({});
|
|
295
|
+
expect(store.isLoading).toBe(false);
|
|
296
|
+
expect(store.isSaving).toBe(false);
|
|
297
|
+
expect(store.isSaved).toBe(false);
|
|
298
|
+
expect(store.showErrors).toBe(false);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
describe('resetToOriginal', () => {
|
|
302
|
+
it('restores data to original values', () => {
|
|
303
|
+
const store = makeStore();
|
|
304
|
+
store.setOriginal(validData);
|
|
305
|
+
store.data = { ...validData, name: 'Changed' };
|
|
306
|
+
store.resetToOriginal();
|
|
307
|
+
expect(store.data).toEqual(validData);
|
|
308
|
+
});
|
|
309
|
+
it('clears errors and showErrors', () => {
|
|
310
|
+
const store = makeStore();
|
|
311
|
+
store.setOriginal(validData);
|
|
312
|
+
store.showErrors = true;
|
|
313
|
+
store.errors = { name: 'err' };
|
|
314
|
+
store.resetToOriginal();
|
|
315
|
+
expect(store.errors).toEqual({});
|
|
316
|
+
expect(store.showErrors).toBe(false);
|
|
317
|
+
});
|
|
318
|
+
it('does nothing when no original data', () => {
|
|
319
|
+
const store = makeStore();
|
|
320
|
+
store.data = validData;
|
|
321
|
+
store.resetToOriginal();
|
|
322
|
+
expect(store.data).toEqual(validData);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
describe('save', () => {
|
|
326
|
+
it('runs save function on valid data', async () => {
|
|
327
|
+
const store = makeStore(validData);
|
|
328
|
+
store.setOriginal(validData);
|
|
329
|
+
store.data = { ...validData, name: 'Updated' };
|
|
330
|
+
const saveFn = vi.fn().mockResolvedValue({ id: 1 });
|
|
331
|
+
const result = await store.save(saveFn);
|
|
332
|
+
expect(saveFn).toHaveBeenCalled();
|
|
333
|
+
expect(result).toEqual({ id: 1 });
|
|
334
|
+
expect(store.isSaved).toBe(true);
|
|
335
|
+
expect(store.isSaving).toBe(false);
|
|
336
|
+
});
|
|
337
|
+
it('returns undefined when validation fails', async () => {
|
|
338
|
+
const store = makeStore();
|
|
339
|
+
const saveFn = vi.fn();
|
|
340
|
+
const result = await store.save(saveFn);
|
|
341
|
+
expect(result).toBeUndefined();
|
|
342
|
+
expect(saveFn).not.toHaveBeenCalled();
|
|
343
|
+
});
|
|
344
|
+
it('updates originalData after successful save', async () => {
|
|
345
|
+
const store = makeStore(validData);
|
|
346
|
+
store.setOriginal(validData);
|
|
347
|
+
const updatedData = { ...validData, name: 'Updated' };
|
|
348
|
+
store.data = updatedData;
|
|
349
|
+
await store.save(async () => 'ok');
|
|
350
|
+
expect(store.originalData).toEqual(updatedData);
|
|
351
|
+
});
|
|
352
|
+
it('resets isSaving even on error', async () => {
|
|
353
|
+
const store = makeStore(validData);
|
|
354
|
+
store.setOriginal(validData);
|
|
355
|
+
store.data = { ...validData, name: 'Updated' };
|
|
356
|
+
const saveFn = vi.fn().mockRejectedValue(new Error('fail'));
|
|
357
|
+
await expect(store.save(saveFn)).rejects.toThrow('fail');
|
|
358
|
+
expect(store.isSaving).toBe(false);
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
describe('canSave', () => {
|
|
362
|
+
it('is true when dirty, valid, and not saving', () => {
|
|
363
|
+
const store = makeStore(validData);
|
|
364
|
+
store.setOriginal(validData);
|
|
365
|
+
store.data = { ...validData, name: 'Updated' };
|
|
366
|
+
expect(store.canSave).toBe(true);
|
|
367
|
+
});
|
|
368
|
+
it('is false when not dirty', () => {
|
|
369
|
+
const store = makeStore(validData);
|
|
370
|
+
store.setOriginal(validData);
|
|
371
|
+
expect(store.canSave).toBe(false);
|
|
372
|
+
});
|
|
373
|
+
it('is false when not valid', () => {
|
|
374
|
+
const store = makeStore();
|
|
375
|
+
store.setOriginal(validData);
|
|
376
|
+
store.data = { ...validData, name: '' };
|
|
377
|
+
expect(store.canSave).toBe(false);
|
|
378
|
+
});
|
|
379
|
+
it('is false when saving', () => {
|
|
380
|
+
const store = makeStore(validData);
|
|
381
|
+
store.setOriginal(validData);
|
|
382
|
+
store.data = { ...validData, name: 'Updated' };
|
|
383
|
+
store.isSaving = true;
|
|
384
|
+
expect(store.canSave).toBe(false);
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized validation messages for Zod schemas
|
|
3
|
+
*/
|
|
4
|
+
export declare const VALIDATION_MESSAGES: {
|
|
5
|
+
readonly REQUIRED: "This field is required";
|
|
6
|
+
readonly INVALID_EMAIL: "Please enter a valid email address";
|
|
7
|
+
readonly INVALID_PHONE: "Please enter a valid phone number";
|
|
8
|
+
readonly INVALID_URL: "Please enter a valid URL";
|
|
9
|
+
readonly INVALID_UUID: "Invalid identifier format";
|
|
10
|
+
readonly INVALID_DATE: "Please enter a valid date";
|
|
11
|
+
readonly DATE_MUST_BE_FUTURE: "Date must be in the future";
|
|
12
|
+
readonly MUST_BE_INTEGER: "Must be a whole number";
|
|
13
|
+
readonly MUST_BE_POSITIVE: "Must be a positive number";
|
|
14
|
+
readonly MUST_BE_NON_NEGATIVE: "Must be zero or greater";
|
|
15
|
+
readonly PRICE_TOO_HIGH: "Price is too high (max $10,000)";
|
|
16
|
+
readonly PERCENTAGE_TOO_HIGH: "Percentage must be 100 or less";
|
|
17
|
+
readonly PASSWORD_TOO_SHORT: (min: number) => string;
|
|
18
|
+
readonly PASSWORD_TOO_LONG: (max: number) => string;
|
|
19
|
+
readonly INVALID_FORMAT: "Invalid format";
|
|
20
|
+
readonly INVALID_TIMEZONE: "Please enter a valid timezone";
|
|
21
|
+
readonly INVALID_STATE: "Please enter a valid 2-letter state code";
|
|
22
|
+
readonly INVALID_ZIP: "Please enter a valid zip code";
|
|
23
|
+
readonly tooLong: (field: string, max: number) => string;
|
|
24
|
+
readonly tooShort: (field: string, min: number) => string;
|
|
25
|
+
readonly tooSmall: (field: string, min: number) => string;
|
|
26
|
+
readonly tooLarge: (field: string, max: number) => string;
|
|
27
|
+
readonly fileTooLarge: (maxMB: number) => string;
|
|
28
|
+
readonly invalidFileType: (types: readonly string[]) => string;
|
|
29
|
+
readonly DISCOUNT_REQUIRED: "A discount amount is required";
|
|
30
|
+
readonly INVALID_DISCOUNT_TYPE: "Invalid discount type";
|
|
31
|
+
readonly END_DATE_AFTER_START: "End date must be after start date";
|
|
32
|
+
readonly MIN_EXCEEDS_MAX: "Minimum cannot exceed maximum";
|
|
33
|
+
readonly ACCEPT_TERMS_REQUIRED: "You must accept the terms and conditions";
|
|
34
|
+
readonly passwordTooShort: (min: number) => string;
|
|
35
|
+
readonly passwordTooLong: (max: number) => string;
|
|
36
|
+
readonly PASSWORD_NEEDS_UPPERCASE: "Password must contain at least one uppercase letter";
|
|
37
|
+
readonly PASSWORD_NEEDS_LOWERCASE: "Password must contain at least one lowercase letter";
|
|
38
|
+
readonly PASSWORD_NEEDS_NUMBER: "Password must contain at least one number";
|
|
39
|
+
readonly PASSWORD_NEEDS_SPECIAL: "Password must contain at least one special character";
|
|
40
|
+
readonly PASSWORDS_MUST_MATCH: "Passwords do not match";
|
|
41
|
+
readonly PASSWORD_SAME_AS_CURRENT: "New password must be different from current password";
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/lib/messages.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;uCA4BJ,MAAM;sCACP,MAAM;;;;;8BASd,MAAM,OAAO,MAAM;+BAClB,MAAM,OAAO,MAAM;+BACnB,MAAM,OAAO,MAAM;+BACnB,MAAM,OAAO,MAAM;mCAGf,MAAM;sCACH,SAAS,MAAM,EAAE;;;;;;qCAclB,MAAM;oCACP,MAAM;;;;;;;CAOrB,CAAC"}
|
package/dist/messages.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized validation messages for Zod schemas
|
|
3
|
+
*/
|
|
4
|
+
export const VALIDATION_MESSAGES = {
|
|
5
|
+
// Required field
|
|
6
|
+
REQUIRED: 'This field is required',
|
|
7
|
+
// Email validation
|
|
8
|
+
INVALID_EMAIL: 'Please enter a valid email address',
|
|
9
|
+
// Phone validation
|
|
10
|
+
INVALID_PHONE: 'Please enter a valid phone number',
|
|
11
|
+
// URL validation
|
|
12
|
+
INVALID_URL: 'Please enter a valid URL',
|
|
13
|
+
// UUID validation
|
|
14
|
+
INVALID_UUID: 'Invalid identifier format',
|
|
15
|
+
// Date validation
|
|
16
|
+
INVALID_DATE: 'Please enter a valid date',
|
|
17
|
+
DATE_MUST_BE_FUTURE: 'Date must be in the future',
|
|
18
|
+
// Number validation
|
|
19
|
+
MUST_BE_INTEGER: 'Must be a whole number',
|
|
20
|
+
MUST_BE_POSITIVE: 'Must be a positive number',
|
|
21
|
+
MUST_BE_NON_NEGATIVE: 'Must be zero or greater',
|
|
22
|
+
PRICE_TOO_HIGH: 'Price is too high (max $10,000)',
|
|
23
|
+
PERCENTAGE_TOO_HIGH: 'Percentage must be 100 or less',
|
|
24
|
+
// Password validation
|
|
25
|
+
PASSWORD_TOO_SHORT: (min) => `Password must be at least ${min} characters`,
|
|
26
|
+
PASSWORD_TOO_LONG: (max) => `Password must be ${max} characters or less`,
|
|
27
|
+
// Format validation
|
|
28
|
+
INVALID_FORMAT: 'Invalid format',
|
|
29
|
+
INVALID_TIMEZONE: 'Please enter a valid timezone',
|
|
30
|
+
INVALID_STATE: 'Please enter a valid 2-letter state code',
|
|
31
|
+
INVALID_ZIP: 'Please enter a valid zip code',
|
|
32
|
+
// Dynamic length messages
|
|
33
|
+
tooLong: (field, max) => `${field} must be ${max} characters or less`,
|
|
34
|
+
tooShort: (field, min) => `${field} must be at least ${min} characters`,
|
|
35
|
+
tooSmall: (field, min) => `${field} must be at least ${min}`,
|
|
36
|
+
tooLarge: (field, max) => `${field} must be no more than ${max}`,
|
|
37
|
+
// File validation
|
|
38
|
+
fileTooLarge: (maxMB) => `File must be ${maxMB}MB or less`,
|
|
39
|
+
invalidFileType: (types) => `File must be one of: ${types.join(', ')}`,
|
|
40
|
+
// Promo code validation
|
|
41
|
+
DISCOUNT_REQUIRED: 'A discount amount is required',
|
|
42
|
+
INVALID_DISCOUNT_TYPE: 'Invalid discount type',
|
|
43
|
+
// Date range validation
|
|
44
|
+
END_DATE_AFTER_START: 'End date must be after start date',
|
|
45
|
+
MIN_EXCEEDS_MAX: 'Minimum cannot exceed maximum',
|
|
46
|
+
// Terms validation
|
|
47
|
+
ACCEPT_TERMS_REQUIRED: 'You must accept the terms and conditions',
|
|
48
|
+
// Password validation (function variants)
|
|
49
|
+
passwordTooShort: (min) => `Password must be at least ${min} characters`,
|
|
50
|
+
passwordTooLong: (max) => `Password must be ${max} characters or less`,
|
|
51
|
+
PASSWORD_NEEDS_UPPERCASE: 'Password must contain at least one uppercase letter',
|
|
52
|
+
PASSWORD_NEEDS_LOWERCASE: 'Password must contain at least one lowercase letter',
|
|
53
|
+
PASSWORD_NEEDS_NUMBER: 'Password must contain at least one number',
|
|
54
|
+
PASSWORD_NEEDS_SPECIAL: 'Password must contain at least one special character',
|
|
55
|
+
PASSWORDS_MUST_MATCH: 'Passwords do not match',
|
|
56
|
+
PASSWORD_SAME_AS_CURRENT: 'New password must be different from current password',
|
|
57
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatActivityNotice.spec.d.ts","sourceRoot":"","sources":["../../../src/lib/patterns/chat/ChatActivityNotice.spec.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/svelte';
|
|
2
|
+
import { expect, describe, test } from 'vitest';
|
|
3
|
+
import ChatActivityNotice from './ChatActivityNotice.svelte';
|
|
4
|
+
|
|
5
|
+
describe('ChatActivityNotice', () => {
|
|
6
|
+
test('renders without crashing', () => {
|
|
7
|
+
const { container } = render(ChatActivityNotice);
|
|
8
|
+
expect(container.firstElementChild).toBeInTheDocument();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('shows actor name', () => {
|
|
12
|
+
render(ChatActivityNotice, { props: { actorName: 'John' } });
|
|
13
|
+
expect(screen.getByText('John')).toBeInTheDocument();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('shows timestamp', () => {
|
|
17
|
+
render(ChatActivityNotice, { props: { timestamp: '2:00 PM' } });
|
|
18
|
+
expect(screen.getByText('2:00 PM')).toBeInTheDocument();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('shows dot separator when both actorName and timestamp', () => {
|
|
22
|
+
const { container } = render(ChatActivityNotice, {
|
|
23
|
+
props: { actorName: 'John', timestamp: '2:00 PM' }
|
|
24
|
+
});
|
|
25
|
+
expect(container.textContent).toContain('·');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('no separator when only actorName', () => {
|
|
29
|
+
const { container } = render(ChatActivityNotice, {
|
|
30
|
+
props: { actorName: 'John' }
|
|
31
|
+
});
|
|
32
|
+
expect(container.textContent).not.toContain('·');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('no separator when only timestamp', () => {
|
|
36
|
+
const { container } = render(ChatActivityNotice, {
|
|
37
|
+
props: { timestamp: '2:00 PM' }
|
|
38
|
+
});
|
|
39
|
+
expect(container.textContent).not.toContain('·');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('hides metadata line when neither actorName nor timestamp', () => {
|
|
43
|
+
const { container } = render(ChatActivityNotice);
|
|
44
|
+
// No actor or timestamp text should appear
|
|
45
|
+
const metaText = container.textContent.trim();
|
|
46
|
+
expect(metaText).toBe('');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('renders decorative lines', () => {
|
|
50
|
+
const { container } = render(ChatActivityNotice, { props: { actorName: 'Test' } });
|
|
51
|
+
const lines = container.querySelectorAll('.bg-gray-200, [class*="h-px"]');
|
|
52
|
+
expect(lines.length).toBeGreaterThanOrEqual(1);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('applies custom className', () => {
|
|
56
|
+
const { container } = render(ChatActivityNotice, { props: { className: 'my-notice' } });
|
|
57
|
+
expect(container.querySelector('.my-notice')).toBeInTheDocument();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatBubble.spec.d.ts","sourceRoot":"","sources":["../../../src/lib/patterns/chat/ChatBubble.spec.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/svelte';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { expect, describe, test, vi } from 'vitest';
|
|
4
|
+
import ChatBubble from './ChatBubble.svelte';
|
|
5
|
+
|
|
6
|
+
describe('ChatBubble', () => {
|
|
7
|
+
test('renders with default inbound direction', () => {
|
|
8
|
+
const { container } = render(ChatBubble);
|
|
9
|
+
const bubble = container.querySelector('.rounded-bl-sm');
|
|
10
|
+
expect(bubble).toBeInTheDocument();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('renders outbound direction', () => {
|
|
14
|
+
const { container } = render(ChatBubble, { props: { direction: 'outbound' } });
|
|
15
|
+
const bubble = container.querySelector('.rounded-br-sm');
|
|
16
|
+
expect(bubble).toBeInTheDocument();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('outbound uses flex-row-reverse', () => {
|
|
20
|
+
const { container } = render(ChatBubble, { props: { direction: 'outbound' } });
|
|
21
|
+
const wrapper = container.querySelector('.flex-row-reverse');
|
|
22
|
+
expect(wrapper).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('outbound uses items-end', () => {
|
|
26
|
+
const { container } = render(ChatBubble, { props: { direction: 'outbound' } });
|
|
27
|
+
const el = container.querySelector('.items-end');
|
|
28
|
+
expect(el).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('inbound uses items-start', () => {
|
|
32
|
+
const { container } = render(ChatBubble);
|
|
33
|
+
const el = container.querySelector('.items-start');
|
|
34
|
+
expect(el).toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('shows sender name for inbound', () => {
|
|
38
|
+
render(ChatBubble, { props: { direction: 'inbound', senderName: 'Alice' } });
|
|
39
|
+
expect(screen.getByText('Alice')).toBeInTheDocument();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('hides sender name for outbound', () => {
|
|
43
|
+
render(ChatBubble, { props: { direction: 'outbound', senderName: 'Alice' } });
|
|
44
|
+
expect(screen.queryByText('Alice')).not.toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('shows timestamp when not failed', () => {
|
|
48
|
+
render(ChatBubble, { props: { timestamp: '2:30 PM' } });
|
|
49
|
+
expect(screen.getByText('2:30 PM')).toBeInTheDocument();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('shows failed indicator', () => {
|
|
53
|
+
const { container } = render(ChatBubble, { props: { failed: true } });
|
|
54
|
+
const redIndicator = container.querySelector('.text-red-500, .bg-red-500, [class*="red"]');
|
|
55
|
+
expect(redIndicator).toBeInTheDocument();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('shows error message when failed with errorMessage', () => {
|
|
59
|
+
render(ChatBubble, { props: { failed: true, errorMessage: 'Send failed' } });
|
|
60
|
+
expect(screen.getByText('Send failed')).toBeInTheDocument();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('shows timestamp even when failed (below error)', () => {
|
|
64
|
+
render(ChatBubble, { props: { failed: true, timestamp: '2:30 PM' } });
|
|
65
|
+
// timestamp still renders; error indicator shown alongside
|
|
66
|
+
expect(screen.getByText('2:30 PM')).toBeInTheDocument();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('shows retry button when failed with onretry', async () => {
|
|
70
|
+
const onretry = vi.fn();
|
|
71
|
+
const { container } = render(ChatBubble, {
|
|
72
|
+
props: { failed: true, errorMessage: 'Error', onretry }
|
|
73
|
+
});
|
|
74
|
+
const button = container.querySelector('button');
|
|
75
|
+
expect(button).toBeInTheDocument();
|
|
76
|
+
await userEvent.click(button);
|
|
77
|
+
expect(onretry).toHaveBeenCalled();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('applies custom className', () => {
|
|
81
|
+
const { container } = render(ChatBubble, { props: { className: 'my-bubble' } });
|
|
82
|
+
const el = container.querySelector('.my-bubble');
|
|
83
|
+
expect(el).toBeInTheDocument();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('no sender name when not provided for inbound', () => {
|
|
87
|
+
const { container } = render(ChatBubble, { props: { direction: 'inbound' } });
|
|
88
|
+
// Should not crash and should render
|
|
89
|
+
expect(container.firstElementChild).toBeInTheDocument();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatContainer.spec.d.ts","sourceRoot":"","sources":["../../../src/lib/patterns/chat/ChatContainer.spec.js"],"names":[],"mappings":""}
|