@sabrenski/spire-ui 0.0.1
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/LICENSE +21 -0
- package/README.md +233 -0
- package/dist/index.d.ts +4981 -0
- package/dist/spire-ui.css +1 -0
- package/dist/spire-ui.es.js +18403 -0
- package/dist/spire-ui.umd.js +45 -0
- package/package.json +83 -0
- package/src/components/Accordion/Accordion.test.ts +218 -0
- package/src/components/Accordion/AccordionContent.vue +112 -0
- package/src/components/Accordion/AccordionItem.vue +87 -0
- package/src/components/Accordion/AccordionRoot.vue +111 -0
- package/src/components/Accordion/AccordionTrigger.vue +125 -0
- package/src/components/Accordion/index.ts +11 -0
- package/src/components/Accordion/keys.ts +23 -0
- package/src/components/Avatar/Avatar.test.ts +181 -0
- package/src/components/Avatar/Avatar.vue +150 -0
- package/src/components/Avatar/index.ts +2 -0
- package/src/components/Badge/Badge.test.ts +141 -0
- package/src/components/Badge/Badge.vue +133 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/BadgeContainer/BadgeContainer.test.ts +150 -0
- package/src/components/BadgeContainer/BadgeContainer.vue +90 -0
- package/src/components/BadgeContainer/index.ts +2 -0
- package/src/components/Breadcrumb/Breadcrumb.test.ts +342 -0
- package/src/components/Breadcrumb/BreadcrumbEllipsis.vue +96 -0
- package/src/components/Breadcrumb/BreadcrumbItem.vue +16 -0
- package/src/components/Breadcrumb/BreadcrumbLink.vue +67 -0
- package/src/components/Breadcrumb/BreadcrumbList.vue +20 -0
- package/src/components/Breadcrumb/BreadcrumbPage.vue +25 -0
- package/src/components/Breadcrumb/BreadcrumbRoot.vue +41 -0
- package/src/components/Breadcrumb/BreadcrumbSeparator.vue +63 -0
- package/src/components/Breadcrumb/index.ts +13 -0
- package/src/components/Breadcrumb/keys.ts +7 -0
- package/src/components/Button/Button.test.ts +231 -0
- package/src/components/Button/Button.vue +349 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/Callout/Callout.test.ts +260 -0
- package/src/components/Callout/Callout.vue +341 -0
- package/src/components/Callout/index.ts +2 -0
- package/src/components/Card/Card.test.ts +565 -0
- package/src/components/Card/Card.vue +209 -0
- package/src/components/Card/CardContent.vue +57 -0
- package/src/components/Card/CardFooter.vue +72 -0
- package/src/components/Card/CardHeader.vue +111 -0
- package/src/components/Card/CardImage.vue +124 -0
- package/src/components/Card/index.ts +14 -0
- package/src/components/Chart/BarChart.vue +208 -0
- package/src/components/Chart/BaseChart.vue +444 -0
- package/src/components/Chart/Chart.test.ts +359 -0
- package/src/components/Chart/DonutChart.vue +283 -0
- package/src/components/Chart/LineChart.vue +211 -0
- package/src/components/Chart/index.ts +20 -0
- package/src/components/Chart/useChartTheme.ts +192 -0
- package/src/components/Checkbox/Checkbox.test.ts +209 -0
- package/src/components/Checkbox/Checkbox.vue +285 -0
- package/src/components/Checkbox/index.ts +2 -0
- package/src/components/ChoiceChip/ChoiceChip.test.ts +142 -0
- package/src/components/ChoiceChip/ChoiceChip.vue +218 -0
- package/src/components/ChoiceChip/index.ts +2 -0
- package/src/components/ChoiceChipGroup/ChoiceChipGroup.test.ts +151 -0
- package/src/components/ChoiceChipGroup/ChoiceChipGroup.vue +70 -0
- package/src/components/ChoiceChipGroup/index.ts +2 -0
- package/src/components/ColorPicker/ColorArea.vue +159 -0
- package/src/components/ColorPicker/ColorPicker.test.ts +250 -0
- package/src/components/ColorPicker/ColorPicker.vue +339 -0
- package/src/components/ColorPicker/ColorSlider.vue +191 -0
- package/src/components/ColorPicker/index.ts +7 -0
- package/src/components/Combobox/Combobox.test.ts +891 -0
- package/src/components/Combobox/Combobox.vue +934 -0
- package/src/components/Combobox/index.ts +2 -0
- package/src/components/DataTable/DataTable.test.ts +1221 -0
- package/src/components/DataTable/DataTable.vue +1415 -0
- package/src/components/DataTable/index.ts +10 -0
- package/src/components/DatePicker/DatePicker.test.ts +625 -0
- package/src/components/DatePicker/DatePicker.vue +1586 -0
- package/src/components/DatePicker/index.ts +2 -0
- package/src/components/Drawer/Drawer.test.ts +336 -0
- package/src/components/Drawer/Drawer.vue +466 -0
- package/src/components/Drawer/index.ts +2 -0
- package/src/components/Dropdown/Dropdown.test.ts +607 -0
- package/src/components/Dropdown/Dropdown.vue +807 -0
- package/src/components/Dropdown/DropdownItem.vue +227 -0
- package/src/components/Dropdown/DropdownSeparator.vue +14 -0
- package/src/components/Dropdown/DropdownSub.vue +104 -0
- package/src/components/Dropdown/DropdownSubContent.vue +187 -0
- package/src/components/Dropdown/DropdownSubTrigger.vue +151 -0
- package/src/components/Dropdown/index.ts +14 -0
- package/src/components/EmptyState/EmptyState.test.ts +180 -0
- package/src/components/EmptyState/EmptyState.vue +137 -0
- package/src/components/EmptyState/index.ts +2 -0
- package/src/components/FileUpload/FileUpload.test.ts +1151 -0
- package/src/components/FileUpload/FileUpload.vue +1042 -0
- package/src/components/FileUpload/index.ts +2 -0
- package/src/components/Heading/Heading.test.ts +107 -0
- package/src/components/Heading/Heading.vue +67 -0
- package/src/components/Heading/index.ts +2 -0
- package/src/components/Icon/Icon.test.ts +157 -0
- package/src/components/Icon/Icon.vue +86 -0
- package/src/components/Icon/index.ts +2 -0
- package/src/components/Input/Input.test.ts +273 -0
- package/src/components/Input/Input.vue +388 -0
- package/src/components/Input/index.ts +2 -0
- package/src/components/Layout/Container.vue +67 -0
- package/src/components/Layout/Grid.vue +159 -0
- package/src/components/Layout/GridItem.vue +154 -0
- package/src/components/Layout/Layout.test.ts +202 -0
- package/src/components/Layout/Stack.vue +128 -0
- package/src/components/Layout/index.ts +9 -0
- package/src/components/Layout/keys.ts +7 -0
- package/src/components/Modal/Modal.test.ts +311 -0
- package/src/components/Modal/Modal.vue +336 -0
- package/src/components/Modal/index.ts +2 -0
- package/src/components/Pagination/Pagination.test.ts +303 -0
- package/src/components/Pagination/Pagination.vue +212 -0
- package/src/components/Pagination/index.ts +3 -0
- package/src/components/Pagination/utils.ts +86 -0
- package/src/components/Popover/Popover.test.ts +285 -0
- package/src/components/Popover/Popover.vue +441 -0
- package/src/components/Popover/index.ts +2 -0
- package/src/components/Progress/Progress.test.ts +361 -0
- package/src/components/Progress/Progress.vue +363 -0
- package/src/components/Progress/index.ts +7 -0
- package/src/components/Radio/Radio.test.ts +216 -0
- package/src/components/Radio/Radio.vue +214 -0
- package/src/components/Radio/index.ts +2 -0
- package/src/components/Rating/Rating.test.ts +319 -0
- package/src/components/Rating/Rating.vue +247 -0
- package/src/components/Rating/index.ts +2 -0
- package/src/components/SegmentedControl/SegmentedControl.test.ts +292 -0
- package/src/components/SegmentedControl/SegmentedControl.vue +288 -0
- package/src/components/SegmentedControl/index.ts +2 -0
- package/src/components/Select/Select.test.ts +589 -0
- package/src/components/Select/Select.vue +666 -0
- package/src/components/Select/index.ts +2 -0
- package/src/components/Sidebar/Sidebar.test.ts +301 -0
- package/src/components/Sidebar/SidebarGroup.vue +103 -0
- package/src/components/Sidebar/SidebarItem.vue +196 -0
- package/src/components/Sidebar/SidebarLayout.vue +42 -0
- package/src/components/Sidebar/SidebarRoot.vue +122 -0
- package/src/components/Sidebar/index.ts +11 -0
- package/src/components/Sidebar/keys.ts +14 -0
- package/src/components/Skeleton/Skeleton.test.ts +130 -0
- package/src/components/Skeleton/Skeleton.vue +104 -0
- package/src/components/Skeleton/index.ts +2 -0
- package/src/components/Slider/Slider.test.ts +416 -0
- package/src/components/Slider/Slider.vue +435 -0
- package/src/components/Slider/index.ts +2 -0
- package/src/components/Slider/utils.ts +91 -0
- package/src/components/Spinner/Spinner.test.ts +79 -0
- package/src/components/Spinner/Spinner.vue +159 -0
- package/src/components/Spinner/index.ts +2 -0
- package/src/components/SpireProvider/SpireProvider.vue +71 -0
- package/src/components/SpireProvider/index.ts +11 -0
- package/src/components/Stepper/Stepper.test.ts +221 -0
- package/src/components/Stepper/StepperContent.vue +51 -0
- package/src/components/Stepper/StepperItem.vue +89 -0
- package/src/components/Stepper/StepperRoot.vue +101 -0
- package/src/components/Stepper/StepperSeparator.vue +52 -0
- package/src/components/Stepper/StepperTrigger.vue +144 -0
- package/src/components/Stepper/index.ts +11 -0
- package/src/components/Stepper/keys.ts +27 -0
- package/src/components/Switch/Switch.test.ts +214 -0
- package/src/components/Switch/Switch.vue +235 -0
- package/src/components/Switch/index.ts +2 -0
- package/src/components/Tabs/Tabs.test.ts +363 -0
- package/src/components/Tabs/Tabs.vue +318 -0
- package/src/components/Tabs/index.ts +2 -0
- package/src/components/Text/Text.test.ts +154 -0
- package/src/components/Text/Text.vue +100 -0
- package/src/components/Text/index.ts +2 -0
- package/src/components/Textarea/Textarea.test.ts +432 -0
- package/src/components/Textarea/Textarea.vue +411 -0
- package/src/components/Textarea/index.ts +2 -0
- package/src/components/TimePicker/TimePicker.test.ts +352 -0
- package/src/components/TimePicker/TimePicker.vue +569 -0
- package/src/components/TimePicker/index.ts +2 -0
- package/src/components/Timeline/Timeline.test.ts +193 -0
- package/src/components/Timeline/Timeline.vue +111 -0
- package/src/components/Timeline/TimelineItem.vue +167 -0
- package/src/components/Timeline/index.ts +13 -0
- package/src/components/Timeline/keys.ts +21 -0
- package/src/components/Toast/ToastItem.test.ts +289 -0
- package/src/components/Toast/ToastItem.vue +370 -0
- package/src/components/Toast/ToastProvider.test.ts +158 -0
- package/src/components/Toast/ToastProvider.vue +181 -0
- package/src/components/Toast/index.ts +83 -0
- package/src/components/Toast/toastState.test.ts +165 -0
- package/src/components/Toast/toastState.ts +161 -0
- package/src/components/ToggleButton/ToggleButton.test.ts +166 -0
- package/src/components/ToggleButton/ToggleButton.vue +197 -0
- package/src/components/ToggleButton/index.ts +2 -0
- package/src/components/ToggleGroup/ToggleGroup.test.ts +181 -0
- package/src/components/ToggleGroup/ToggleGroup.vue +130 -0
- package/src/components/ToggleGroup/index.ts +2 -0
- package/src/components/Tooltip/Tooltip.test.ts +238 -0
- package/src/components/Tooltip/Tooltip.vue +217 -0
- package/src/components/Tooltip/index.ts +2 -0
- package/src/components/TreeView/TreeView.test.ts +357 -0
- package/src/components/TreeView/TreeView.vue +251 -0
- package/src/components/TreeView/TreeViewItem.vue +288 -0
- package/src/components/TreeView/index.ts +11 -0
- package/src/components/TreeView/keys.ts +35 -0
- package/src/composables/index.ts +12 -0
- package/src/composables/useClickOutside.ts +36 -0
- package/src/composables/useClipboard.ts +35 -0
- package/src/composables/useEventListener.ts +48 -0
- package/src/composables/useFocusTrap.ts +58 -0
- package/src/composables/useHoverReveal.ts +98 -0
- package/src/composables/useId.ts +10 -0
- package/src/composables/useMagnetic.ts +171 -0
- package/src/composables/useRelativePosition.ts +127 -0
- package/src/composables/useRipple.ts +146 -0
- package/src/composables/useScrollLock.ts +25 -0
- package/src/composables/useSpireConfig.ts +27 -0
- package/src/composables/useStagger.ts +224 -0
- package/src/config/icons.test.ts +115 -0
- package/src/config/icons.ts +170 -0
- package/src/index.ts +361 -0
- package/src/styles/depth.css +129 -0
- package/src/styles/effects.css +169 -0
- package/src/styles/fallback.css +152 -0
- package/src/styles/main.css +25 -0
- package/src/styles/mood.css +211 -0
- package/src/styles/motion.css +159 -0
- package/src/styles/reset.css +97 -0
- package/src/styles/theme.css +708 -0
- package/src/styles/tokens.css +183 -0
- package/src/utils/.gitkeep +0 -0
- package/src/utils/color.ts +277 -0
- package/src/utils/date.test.ts +522 -0
- package/src/utils/date.ts +380 -0
- package/src/utils/index.ts +23 -0
- package/src/utils/object.test.ts +80 -0
- package/src/utils/object.ts +25 -0
- package/src/utils/string.test.ts +64 -0
- package/src/utils/string.ts +32 -0
- package/src/utils/time.ts +156 -0
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
generateCalendarGrid,
|
|
4
|
+
getDaysInMonth,
|
|
5
|
+
formatDate,
|
|
6
|
+
parseDate,
|
|
7
|
+
isSameDate,
|
|
8
|
+
isDateBefore,
|
|
9
|
+
isDateAfter,
|
|
10
|
+
getMonthName,
|
|
11
|
+
getMonthNamesShort,
|
|
12
|
+
getWeekdayNames,
|
|
13
|
+
isDateInRange,
|
|
14
|
+
isDateBetween,
|
|
15
|
+
getRangeClass,
|
|
16
|
+
normalizeRange,
|
|
17
|
+
generateYearGrid,
|
|
18
|
+
getPresetRange
|
|
19
|
+
} from './date'
|
|
20
|
+
|
|
21
|
+
describe('date utilities', () => {
|
|
22
|
+
describe('generateCalendarGrid', () => {
|
|
23
|
+
it('returns 42 items (6 rows × 7 columns)', () => {
|
|
24
|
+
const grid = generateCalendarGrid(2024, 5)
|
|
25
|
+
expect(grid.length).toBe(42)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('marks current month days correctly', () => {
|
|
29
|
+
const grid = generateCalendarGrid(2024, 5)
|
|
30
|
+
const currentMonthDays = grid.filter(d => d.currentMonth)
|
|
31
|
+
expect(currentMonthDays.length).toBe(30)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('includes padding days from previous month', () => {
|
|
35
|
+
const grid = generateCalendarGrid(2024, 5)
|
|
36
|
+
const firstDay = grid[0]
|
|
37
|
+
expect(firstDay.date.getMonth()).toBe(4)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('includes padding days from next month', () => {
|
|
41
|
+
const grid = generateCalendarGrid(2024, 5)
|
|
42
|
+
const lastDay = grid[41]
|
|
43
|
+
expect(lastDay.date.getMonth()).toBe(6)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('starts week on Sunday', () => {
|
|
47
|
+
const grid = generateCalendarGrid(2024, 0)
|
|
48
|
+
expect(grid[0].date.getDay()).toBe(0)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('handles leap year February correctly', () => {
|
|
52
|
+
const grid = generateCalendarGrid(2024, 1)
|
|
53
|
+
const febDays = grid.filter(d => d.currentMonth)
|
|
54
|
+
expect(febDays.length).toBe(29)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('handles non-leap year February correctly', () => {
|
|
58
|
+
const grid = generateCalendarGrid(2023, 1)
|
|
59
|
+
const febDays = grid.filter(d => d.currentMonth)
|
|
60
|
+
expect(febDays.length).toBe(28)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe('getDaysInMonth', () => {
|
|
65
|
+
it('returns 31 for January', () => {
|
|
66
|
+
expect(getDaysInMonth(2024, 0)).toBe(31)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('returns 28 for February in non-leap year', () => {
|
|
70
|
+
expect(getDaysInMonth(2023, 1)).toBe(28)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('returns 29 for February in leap year', () => {
|
|
74
|
+
expect(getDaysInMonth(2024, 1)).toBe(29)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('returns 30 for April', () => {
|
|
78
|
+
expect(getDaysInMonth(2024, 3)).toBe(30)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('handles century years correctly (not leap years)', () => {
|
|
82
|
+
expect(getDaysInMonth(1900, 1)).toBe(28)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('handles 400-year cycle leap years correctly', () => {
|
|
86
|
+
expect(getDaysInMonth(2000, 1)).toBe(29)
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
describe('formatDate', () => {
|
|
91
|
+
it('formats date to ISO 8601 string', () => {
|
|
92
|
+
const date = new Date(2024, 5, 15)
|
|
93
|
+
expect(formatDate(date)).toBe('2024-06-15')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('pads single-digit month with zero', () => {
|
|
97
|
+
const date = new Date(2024, 0, 15)
|
|
98
|
+
expect(formatDate(date)).toBe('2024-01-15')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('pads single-digit day with zero', () => {
|
|
102
|
+
const date = new Date(2024, 5, 5)
|
|
103
|
+
expect(formatDate(date)).toBe('2024-06-05')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('handles year boundaries', () => {
|
|
107
|
+
const date = new Date(2024, 11, 31)
|
|
108
|
+
expect(formatDate(date)).toBe('2024-12-31')
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe('parseDate', () => {
|
|
113
|
+
it('parses valid ISO date string', () => {
|
|
114
|
+
const date = parseDate('2024-06-15')
|
|
115
|
+
expect(date).not.toBeNull()
|
|
116
|
+
expect(date?.getFullYear()).toBe(2024)
|
|
117
|
+
expect(date?.getMonth()).toBe(5)
|
|
118
|
+
expect(date?.getDate()).toBe(15)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('returns null for empty string', () => {
|
|
122
|
+
expect(parseDate('')).toBeNull()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('returns null for invalid format', () => {
|
|
126
|
+
expect(parseDate('15-06-2024')).toBeNull()
|
|
127
|
+
expect(parseDate('2024/06/15')).toBeNull()
|
|
128
|
+
expect(parseDate('June 15, 2024')).toBeNull()
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('returns null for invalid date values', () => {
|
|
132
|
+
expect(parseDate('2024-13-01')).toBeNull()
|
|
133
|
+
expect(parseDate('2024-02-30')).toBeNull()
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('returns null for partial dates', () => {
|
|
137
|
+
expect(parseDate('2024-06')).toBeNull()
|
|
138
|
+
expect(parseDate('2024')).toBeNull()
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
describe('isSameDate', () => {
|
|
143
|
+
it('returns true for same dates', () => {
|
|
144
|
+
const d1 = new Date(2024, 5, 15)
|
|
145
|
+
const d2 = new Date(2024, 5, 15)
|
|
146
|
+
expect(isSameDate(d1, d2)).toBe(true)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('returns true for same dates with different times', () => {
|
|
150
|
+
const d1 = new Date(2024, 5, 15, 10, 30)
|
|
151
|
+
const d2 = new Date(2024, 5, 15, 18, 45)
|
|
152
|
+
expect(isSameDate(d1, d2)).toBe(true)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('returns false for different dates', () => {
|
|
156
|
+
const d1 = new Date(2024, 5, 15)
|
|
157
|
+
const d2 = new Date(2024, 5, 16)
|
|
158
|
+
expect(isSameDate(d1, d2)).toBe(false)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('returns false when first date is null', () => {
|
|
162
|
+
expect(isSameDate(null, new Date())).toBe(false)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('returns false when second date is null', () => {
|
|
166
|
+
expect(isSameDate(new Date(), null)).toBe(false)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('returns false when both dates are null', () => {
|
|
170
|
+
expect(isSameDate(null, null)).toBe(false)
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
describe('isDateBefore', () => {
|
|
175
|
+
it('returns true when date is before reference', () => {
|
|
176
|
+
const date = new Date(2024, 5, 14)
|
|
177
|
+
const reference = new Date(2024, 5, 15)
|
|
178
|
+
expect(isDateBefore(date, reference)).toBe(true)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('returns false when date is same as reference', () => {
|
|
182
|
+
const date = new Date(2024, 5, 15)
|
|
183
|
+
const reference = new Date(2024, 5, 15)
|
|
184
|
+
expect(isDateBefore(date, reference)).toBe(false)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('returns false when date is after reference', () => {
|
|
188
|
+
const date = new Date(2024, 5, 16)
|
|
189
|
+
const reference = new Date(2024, 5, 15)
|
|
190
|
+
expect(isDateBefore(date, reference)).toBe(false)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('ignores time components', () => {
|
|
194
|
+
const date = new Date(2024, 5, 14, 23, 59)
|
|
195
|
+
const reference = new Date(2024, 5, 15, 0, 1)
|
|
196
|
+
expect(isDateBefore(date, reference)).toBe(true)
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
describe('isDateAfter', () => {
|
|
201
|
+
it('returns true when date is after reference', () => {
|
|
202
|
+
const date = new Date(2024, 5, 16)
|
|
203
|
+
const reference = new Date(2024, 5, 15)
|
|
204
|
+
expect(isDateAfter(date, reference)).toBe(true)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('returns false when date is same as reference', () => {
|
|
208
|
+
const date = new Date(2024, 5, 15)
|
|
209
|
+
const reference = new Date(2024, 5, 15)
|
|
210
|
+
expect(isDateAfter(date, reference)).toBe(false)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('returns false when date is before reference', () => {
|
|
214
|
+
const date = new Date(2024, 5, 14)
|
|
215
|
+
const reference = new Date(2024, 5, 15)
|
|
216
|
+
expect(isDateAfter(date, reference)).toBe(false)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it('ignores time components', () => {
|
|
220
|
+
const date = new Date(2024, 5, 16, 0, 1)
|
|
221
|
+
const reference = new Date(2024, 5, 15, 23, 59)
|
|
222
|
+
expect(isDateAfter(date, reference)).toBe(true)
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
describe('getMonthName', () => {
|
|
227
|
+
it('returns month name for given index', () => {
|
|
228
|
+
const name = getMonthName(0)
|
|
229
|
+
expect(name.toLowerCase()).toContain('jan')
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('returns correct month for all indices', () => {
|
|
233
|
+
const months = Array.from({ length: 12 }, (_, i) => getMonthName(i))
|
|
234
|
+
expect(months.length).toBe(12)
|
|
235
|
+
expect(new Set(months).size).toBe(12)
|
|
236
|
+
})
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
describe('getWeekdayNames', () => {
|
|
240
|
+
it('returns 7 weekday names', () => {
|
|
241
|
+
const names = getWeekdayNames()
|
|
242
|
+
expect(names.length).toBe(7)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('starts with Sunday', () => {
|
|
246
|
+
const names = getWeekdayNames('en-US')
|
|
247
|
+
expect(names[0].toLowerCase()).toContain('sun')
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('all names are unique', () => {
|
|
251
|
+
const names = getWeekdayNames()
|
|
252
|
+
expect(new Set(names).size).toBe(7)
|
|
253
|
+
})
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
describe('isDateInRange', () => {
|
|
257
|
+
const testDate = new Date(2024, 5, 15)
|
|
258
|
+
const minDate = new Date(2024, 5, 10)
|
|
259
|
+
const maxDate = new Date(2024, 5, 20)
|
|
260
|
+
|
|
261
|
+
it('returns true when date is within range', () => {
|
|
262
|
+
expect(isDateInRange(testDate, minDate, maxDate)).toBe(true)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it('returns true when date equals min date', () => {
|
|
266
|
+
expect(isDateInRange(minDate, minDate, maxDate)).toBe(true)
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
it('returns true when date equals max date', () => {
|
|
270
|
+
expect(isDateInRange(maxDate, minDate, maxDate)).toBe(true)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('returns false when date is before min', () => {
|
|
274
|
+
const beforeMin = new Date(2024, 5, 5)
|
|
275
|
+
expect(isDateInRange(beforeMin, minDate, maxDate)).toBe(false)
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
it('returns false when date is after max', () => {
|
|
279
|
+
const afterMax = new Date(2024, 5, 25)
|
|
280
|
+
expect(isDateInRange(afterMax, minDate, maxDate)).toBe(false)
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
it('returns true when no constraints', () => {
|
|
284
|
+
expect(isDateInRange(testDate, null, null)).toBe(true)
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it('returns true when only min constraint and date is after', () => {
|
|
288
|
+
expect(isDateInRange(testDate, minDate, null)).toBe(true)
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
it('returns true when only max constraint and date is before', () => {
|
|
292
|
+
expect(isDateInRange(testDate, null, maxDate)).toBe(true)
|
|
293
|
+
})
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
describe('isDateBetween', () => {
|
|
297
|
+
const start = new Date(2024, 5, 10)
|
|
298
|
+
const end = new Date(2024, 5, 20)
|
|
299
|
+
|
|
300
|
+
it('returns true when date is between start and end', () => {
|
|
301
|
+
const date = new Date(2024, 5, 15)
|
|
302
|
+
expect(isDateBetween(date, start, end)).toBe(true)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('returns false when date equals start', () => {
|
|
306
|
+
expect(isDateBetween(start, start, end)).toBe(false)
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it('returns false when date equals end', () => {
|
|
310
|
+
expect(isDateBetween(end, start, end)).toBe(false)
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
it('returns false when date is before start', () => {
|
|
314
|
+
const date = new Date(2024, 5, 5)
|
|
315
|
+
expect(isDateBetween(date, start, end)).toBe(false)
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
it('returns false when date is after end', () => {
|
|
319
|
+
const date = new Date(2024, 5, 25)
|
|
320
|
+
expect(isDateBetween(date, start, end)).toBe(false)
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
it('returns false when start is null', () => {
|
|
324
|
+
const date = new Date(2024, 5, 15)
|
|
325
|
+
expect(isDateBetween(date, null, end)).toBe(false)
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
it('returns false when end is null', () => {
|
|
329
|
+
const date = new Date(2024, 5, 15)
|
|
330
|
+
expect(isDateBetween(date, start, null)).toBe(false)
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
it('handles reversed range (end before start)', () => {
|
|
334
|
+
const date = new Date(2024, 5, 15)
|
|
335
|
+
expect(isDateBetween(date, end, start)).toBe(true)
|
|
336
|
+
})
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
describe('getRangeClass', () => {
|
|
340
|
+
const start = new Date(2024, 5, 10)
|
|
341
|
+
const end = new Date(2024, 5, 20)
|
|
342
|
+
|
|
343
|
+
it('returns "start" for range start date', () => {
|
|
344
|
+
expect(getRangeClass(start, start, end, null)).toBe('start')
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
it('returns "end" for range end date', () => {
|
|
348
|
+
expect(getRangeClass(end, start, end, null)).toBe('end')
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
it('returns "in-range" for dates between start and end', () => {
|
|
352
|
+
const middle = new Date(2024, 5, 15)
|
|
353
|
+
expect(getRangeClass(middle, start, end, null)).toBe('in-range')
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
it('returns null for dates outside range', () => {
|
|
357
|
+
const outside = new Date(2024, 5, 25)
|
|
358
|
+
expect(getRangeClass(outside, start, end, null)).toBe(null)
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
it('returns null when start is null', () => {
|
|
362
|
+
expect(getRangeClass(start, null, end, null)).toBe(null)
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
it('uses hover as effective end when selecting', () => {
|
|
366
|
+
const hover = new Date(2024, 5, 15)
|
|
367
|
+
expect(getRangeClass(hover, start, null, hover)).toBe('end')
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it('returns "start" when only start is selected (no hover)', () => {
|
|
371
|
+
expect(getRangeClass(start, start, null, null)).toBe('start')
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
it('handles reversed selection (hover before start)', () => {
|
|
375
|
+
const hover = new Date(2024, 5, 5)
|
|
376
|
+
expect(getRangeClass(hover, start, null, hover)).toBe('start')
|
|
377
|
+
expect(getRangeClass(start, start, null, hover)).toBe('end')
|
|
378
|
+
})
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
describe('normalizeRange', () => {
|
|
382
|
+
it('returns dates in order when start is before end', () => {
|
|
383
|
+
const start = new Date(2024, 5, 10)
|
|
384
|
+
const end = new Date(2024, 5, 20)
|
|
385
|
+
const [resultStart, resultEnd] = normalizeRange(start, end)
|
|
386
|
+
expect(resultStart).toBe(start)
|
|
387
|
+
expect(resultEnd).toBe(end)
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
it('swaps dates when start is after end', () => {
|
|
391
|
+
const start = new Date(2024, 5, 20)
|
|
392
|
+
const end = new Date(2024, 5, 10)
|
|
393
|
+
const [resultStart, resultEnd] = normalizeRange(start, end)
|
|
394
|
+
expect(resultStart).toBe(end)
|
|
395
|
+
expect(resultEnd).toBe(start)
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
it('returns same date for both when dates are equal', () => {
|
|
399
|
+
const date = new Date(2024, 5, 15)
|
|
400
|
+
const [resultStart, resultEnd] = normalizeRange(date, date)
|
|
401
|
+
expect(resultStart).toBe(date)
|
|
402
|
+
expect(resultEnd).toBe(date)
|
|
403
|
+
})
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
describe('getMonthNamesShort', () => {
|
|
407
|
+
it('returns 12 month names', () => {
|
|
408
|
+
const names = getMonthNamesShort()
|
|
409
|
+
expect(names.length).toBe(12)
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
it('all names are unique', () => {
|
|
413
|
+
const names = getMonthNamesShort()
|
|
414
|
+
expect(new Set(names).size).toBe(12)
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
it('returns short month names', () => {
|
|
418
|
+
const names = getMonthNamesShort('en-US')
|
|
419
|
+
expect(names[0].length).toBeLessThanOrEqual(4)
|
|
420
|
+
})
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
describe('generateYearGrid', () => {
|
|
424
|
+
it('returns 20 years', () => {
|
|
425
|
+
const years = generateYearGrid(2024)
|
|
426
|
+
expect(years.length).toBe(20)
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
it('starts from a multiple of 20', () => {
|
|
430
|
+
const years = generateYearGrid(2024)
|
|
431
|
+
expect(years[0] % 20).toBe(0)
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
it('contains consecutive years', () => {
|
|
435
|
+
const years = generateYearGrid(2024)
|
|
436
|
+
for (let i = 1; i < years.length; i++) {
|
|
437
|
+
expect(years[i]).toBe(years[i - 1] + 1)
|
|
438
|
+
}
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
it('contains the center year', () => {
|
|
442
|
+
const years = generateYearGrid(2024)
|
|
443
|
+
expect(years).toContain(2024)
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
it('handles different decades', () => {
|
|
447
|
+
const years2000 = generateYearGrid(2005)
|
|
448
|
+
expect(years2000[0]).toBe(2000)
|
|
449
|
+
expect(years2000[years2000.length - 1]).toBe(2019)
|
|
450
|
+
|
|
451
|
+
const years2020 = generateYearGrid(2025)
|
|
452
|
+
expect(years2020[0]).toBe(2020)
|
|
453
|
+
expect(years2020[years2020.length - 1]).toBe(2039)
|
|
454
|
+
})
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
describe('getPresetRange', () => {
|
|
458
|
+
it('returns today for "today" preset', () => {
|
|
459
|
+
const [start, end] = getPresetRange('today')
|
|
460
|
+
const today = new Date()
|
|
461
|
+
expect(start.getDate()).toBe(today.getDate())
|
|
462
|
+
expect(end.getDate()).toBe(today.getDate())
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
it('returns yesterday for "yesterday" preset', () => {
|
|
466
|
+
const [start, end] = getPresetRange('yesterday')
|
|
467
|
+
const yesterday = new Date()
|
|
468
|
+
yesterday.setDate(yesterday.getDate() - 1)
|
|
469
|
+
expect(start.getDate()).toBe(yesterday.getDate())
|
|
470
|
+
expect(end.getDate()).toBe(yesterday.getDate())
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
it('returns 7-day range for "last7days" preset', () => {
|
|
474
|
+
const [start, end] = getPresetRange('last7days')
|
|
475
|
+
const diffMs = end.getTime() - start.getTime()
|
|
476
|
+
const diffDays = Math.round(diffMs / (1000 * 60 * 60 * 24))
|
|
477
|
+
expect(diffDays).toBe(6)
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
it('returns 30-day range for "last30days" preset', () => {
|
|
481
|
+
const [start, end] = getPresetRange('last30days')
|
|
482
|
+
const diffMs = end.getTime() - start.getTime()
|
|
483
|
+
const diffDays = Math.round(diffMs / (1000 * 60 * 60 * 24))
|
|
484
|
+
expect(diffDays).toBe(29)
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
it('returns current month for "thisMonth" preset', () => {
|
|
488
|
+
const [start, end] = getPresetRange('thisMonth')
|
|
489
|
+
const today = new Date()
|
|
490
|
+
expect(start.getMonth()).toBe(today.getMonth())
|
|
491
|
+
expect(end.getMonth()).toBe(today.getMonth())
|
|
492
|
+
expect(start.getDate()).toBe(1)
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
it('returns previous month for "lastMonth" preset', () => {
|
|
496
|
+
const [start, end] = getPresetRange('lastMonth')
|
|
497
|
+
const today = new Date()
|
|
498
|
+
const lastMonth = today.getMonth() === 0 ? 11 : today.getMonth() - 1
|
|
499
|
+
expect(start.getMonth()).toBe(lastMonth)
|
|
500
|
+
expect(end.getMonth()).toBe(lastMonth)
|
|
501
|
+
expect(start.getDate()).toBe(1)
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
it('returns current year for "thisYear" preset', () => {
|
|
505
|
+
const [start, end] = getPresetRange('thisYear')
|
|
506
|
+
const today = new Date()
|
|
507
|
+
expect(start.getFullYear()).toBe(today.getFullYear())
|
|
508
|
+
expect(end.getFullYear()).toBe(today.getFullYear())
|
|
509
|
+
expect(start.getMonth()).toBe(0)
|
|
510
|
+
expect(start.getDate()).toBe(1)
|
|
511
|
+
expect(end.getMonth()).toBe(11)
|
|
512
|
+
expect(end.getDate()).toBe(31)
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
it('returns today for unknown preset', () => {
|
|
516
|
+
const [start, end] = getPresetRange('unknown')
|
|
517
|
+
const today = new Date()
|
|
518
|
+
expect(start.getDate()).toBe(today.getDate())
|
|
519
|
+
expect(end.getDate()).toBe(today.getDate())
|
|
520
|
+
})
|
|
521
|
+
})
|
|
522
|
+
})
|