@davidbirchall/core 1.0.8 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/Button/types.d.ts +4 -0
  2. package/dist/Calendar/types.d.ts +22 -0
  3. package/{src/components/Card/types.ts → dist/Card/types.d.ts} +1 -1
  4. package/dist/Checkbox/types.d.ts +7 -0
  5. package/dist/DataTable/types.d.ts +11 -0
  6. package/dist/Dropdown/types.d.ts +13 -0
  7. package/dist/EmptyState/types.d.ts +8 -0
  8. package/dist/ErrorSummary/types.d.ts +4 -0
  9. package/dist/Heading/types.d.ts +6 -0
  10. package/dist/Input/types.d.ts +11 -0
  11. package/dist/Select/types.d.ts +15 -0
  12. package/dist/StatCard/types.d.ts +12 -0
  13. package/dist/Tag/types.d.ts +4 -0
  14. package/dist/TextArea/types.d.ts +11 -0
  15. package/dist/core.css +1 -0
  16. package/dist/core.js +24 -0
  17. package/dist/core.js.map +1 -0
  18. package/dist/core.umd.cjs +2 -0
  19. package/dist/core.umd.cjs.map +1 -0
  20. package/dist/index.d.ts +2 -0
  21. package/dist/package.json +27 -0
  22. package/package.json +4 -1
  23. package/.storybook/main.ts +0 -18
  24. package/.storybook/preview.ts +0 -14
  25. package/src/components/Badge/Badge.stories.ts +0 -147
  26. package/src/components/Badge/Badge.test.ts +0 -57
  27. package/src/components/Badge/Badge.vue +0 -79
  28. package/src/components/Button/Button.stories.ts +0 -80
  29. package/src/components/Button/Button.test.ts +0 -145
  30. package/src/components/Button/Button.vue +0 -108
  31. package/src/components/Button/types.ts +0 -4
  32. package/src/components/Calendar/Calendar.stories.ts +0 -261
  33. package/src/components/Calendar/Calendar.test.ts +0 -119
  34. package/src/components/Calendar/Calendar.vue +0 -528
  35. package/src/components/Calendar/types.ts +0 -20
  36. package/src/components/Card/Card.stories.ts +0 -88
  37. package/src/components/Card/Card.test.ts +0 -173
  38. package/src/components/Card/Card.vue +0 -59
  39. package/src/components/Checkbox/Checkbox.stories.ts +0 -126
  40. package/src/components/Checkbox/Checkbox.test.ts +0 -155
  41. package/src/components/Checkbox/Checkbox.vue +0 -121
  42. package/src/components/Checkbox/types.ts +0 -7
  43. package/src/components/DataTable/DataTable.stories.ts +0 -156
  44. package/src/components/DataTable/DataTable.test.ts +0 -185
  45. package/src/components/DataTable/DataTable.vue +0 -177
  46. package/src/components/DataTable/types.ts +0 -12
  47. package/src/components/DatePicker/DatePicker.stories.ts +0 -172
  48. package/src/components/DatePicker/DatePicker.test.ts +0 -87
  49. package/src/components/DatePicker/DatePicker.vue +0 -302
  50. package/src/components/Dropdown/Dropdown.stories.ts +0 -231
  51. package/src/components/Dropdown/Dropdown.vue +0 -314
  52. package/src/components/Dropdown/types.ts +0 -14
  53. package/src/components/EmptyState/EmptyState.stories.ts +0 -189
  54. package/src/components/EmptyState/EmptyState.vue +0 -215
  55. package/src/components/EmptyState/types.ts +0 -8
  56. package/src/components/ErrorSummary/ErrorSummary.vue +0 -78
  57. package/src/components/ErrorSummary/types.ts +0 -4
  58. package/src/components/FormGroup/FormGroup.stories.ts +0 -264
  59. package/src/components/FormGroup/FormGroup.test.ts +0 -63
  60. package/src/components/FormGroup/FormGroup.vue +0 -58
  61. package/src/components/Heading/Heading.stories.ts +0 -121
  62. package/src/components/Heading/Heading.test.ts +0 -184
  63. package/src/components/Heading/Heading.vue +0 -95
  64. package/src/components/Heading/types.ts +0 -6
  65. package/src/components/Input/Input.stories.ts +0 -172
  66. package/src/components/Input/Input.test.ts +0 -213
  67. package/src/components/Input/Input.vue +0 -121
  68. package/src/components/Input/types.ts +0 -11
  69. package/src/components/Modal/Modal.stories.ts +0 -341
  70. package/src/components/Modal/Modal.test.ts +0 -99
  71. package/src/components/Modal/Modal.vue +0 -278
  72. package/src/components/ProgressBar/ProgressBar.stories.ts +0 -313
  73. package/src/components/ProgressBar/ProgressBar.test.ts +0 -98
  74. package/src/components/ProgressBar/ProgressBar.vue +0 -117
  75. package/src/components/Select/Select.stories.ts +0 -177
  76. package/src/components/Select/Select.test.ts +0 -225
  77. package/src/components/Select/Select.vue +0 -147
  78. package/src/components/Select/types.ts +0 -16
  79. package/src/components/StatCard/StatCard.stories.ts +0 -274
  80. package/src/components/StatCard/StatCard.vue +0 -226
  81. package/src/components/StatCard/types.ts +0 -12
  82. package/src/components/Tag/Tag.stories.ts +0 -78
  83. package/src/components/Tag/Tag.test.ts +0 -50
  84. package/src/components/Tag/Tag.vue +0 -71
  85. package/src/components/Tag/types.ts +0 -4
  86. package/src/components/TextArea/TextArea.stories.ts +0 -171
  87. package/src/components/TextArea/TextArea.test.ts +0 -202
  88. package/src/components/TextArea/TextArea.vue +0 -122
  89. package/src/components/TextArea/types.ts +0 -11
  90. package/src/components/index.ts +0 -5
  91. package/src/test/setup.ts +0 -1
  92. package/src/vite-env.d.ts +0 -6
  93. package/tsconfig.json +0 -29
  94. package/vite.config.ts +0 -33
  95. package/vitest.config.ts +0 -28
@@ -1,145 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest'
2
- import { render, screen } from '@testing-library/vue'
3
- import userEvent from '@testing-library/user-event'
4
- import Button from './Button.vue'
5
-
6
- describe('Button', () => {
7
- describe('rendering', () => {
8
- it('renders slot content', () => {
9
- render(Button, {
10
- slots: {
11
- default: 'Click me'
12
- }
13
- })
14
-
15
- expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument()
16
- })
17
-
18
- it('applies primary variant class by default', () => {
19
- render(Button, {
20
- slots: { default: 'Button' }
21
- })
22
-
23
- const button = screen.getByRole('button')
24
- expect(button).toHaveClass('btn--primary')
25
- })
26
-
27
- it('applies correct variant class', () => {
28
- render(Button, {
29
- props: {
30
- variant: 'secondary'
31
- },
32
- slots: { default: 'Button' }
33
- })
34
-
35
- const button = screen.getByRole('button')
36
- expect(button).toHaveClass('btn--secondary')
37
- expect(button).not.toHaveClass('btn--primary')
38
- })
39
-
40
- it('applies danger variant class', () => {
41
- render(Button, {
42
- props: {
43
- variant: 'danger'
44
- },
45
- slots: { default: 'Button' }
46
- })
47
-
48
- const button = screen.getByRole('button')
49
- expect(button).toHaveClass('btn--danger')
50
- })
51
-
52
- it('applies disabled class when disabled prop is true', () => {
53
- render(Button, {
54
- props: {
55
- disabled: true
56
- },
57
- slots: { default: 'Button' }
58
- })
59
-
60
- const button = screen.getByRole('button')
61
- expect(button).toHaveClass('btn--disabled')
62
- })
63
-
64
- it('sets disabled attribute when disabled prop is true', () => {
65
- render(Button, {
66
- props: {
67
- disabled: true
68
- },
69
- slots: { default: 'Button' }
70
- })
71
-
72
- const button = screen.getByRole('button')
73
- expect(button).toBeDisabled()
74
- })
75
- })
76
-
77
- describe('interactions', () => {
78
- it('emits click event when clicked', async () => {
79
- const user = userEvent.setup()
80
- const clickHandler = vi.fn()
81
-
82
- render(Button, {
83
- props: {
84
- onClick: clickHandler
85
- },
86
- slots: { default: 'Click me' }
87
- })
88
-
89
- const button = screen.getByRole('button')
90
- await user.click(button)
91
-
92
- expect(clickHandler).toHaveBeenCalledTimes(1)
93
- expect(clickHandler).toHaveBeenCalledWith(expect.any(MouseEvent))
94
- })
95
-
96
- it('does not call click handler when disabled', async () => {
97
- const user = userEvent.setup()
98
- const clickHandler = vi.fn()
99
-
100
- render(Button, {
101
- props: {
102
- disabled: true,
103
- onClick: clickHandler
104
- },
105
- slots: { default: 'Button' }
106
- })
107
-
108
- const button = screen.getByRole('button')
109
- await user.click(button)
110
-
111
- expect(clickHandler).not.toHaveBeenCalled()
112
- })
113
- })
114
-
115
- describe('props validation', () => {
116
- it('accepts all valid variant values', () => {
117
- const variants = ['primary', 'secondary', 'danger'] as const
118
-
119
- variants.forEach(variant => {
120
- const { container } = render(Button, {
121
- props: {
122
- variant
123
- },
124
- slots: { default: 'Button' }
125
- })
126
-
127
- const button = container.querySelector('button')
128
- expect(button).toHaveClass(`btn--${variant}`)
129
- })
130
- })
131
-
132
- it('handles boolean disabled prop', () => {
133
- render(Button, {
134
- props: {
135
- disabled: false
136
- },
137
- slots: { default: 'Button' }
138
- })
139
-
140
- const button = screen.getByRole('button')
141
- expect(button).not.toHaveClass('btn--disabled')
142
- expect(button).not.toBeDisabled()
143
- })
144
- })
145
- })
@@ -1,108 +0,0 @@
1
- <template>
2
- <component
3
- :is="url ? 'a' : 'button'"
4
- :href="url"
5
- :target="url ? target : undefined"
6
- :rel="url && target === '_blank' ? 'noopener noreferrer' : undefined"
7
- :class="['btn', `btn--${variant}`, { 'btn--disabled': disabled }]"
8
- :disabled="!url && disabled"
9
- :aria-disabled="disabled"
10
- v-on="!url ? { click: handleClick } : {}"
11
- >
12
- <slot />
13
- <span v-if="url && target === '_blank'" class="sr-only">(opens in new window)</span>
14
- </component>
15
- </template>
16
-
17
- <script setup lang="ts">
18
- export interface ButtonProps {
19
- variant?: 'primary' | 'secondary' | 'danger'
20
- disabled?: boolean
21
- url?: string
22
- target?: '_self' | '_blank'
23
- }
24
-
25
- const props = withDefaults(defineProps<ButtonProps>(), {
26
- variant: 'primary',
27
- disabled: false,
28
- target: '_self'
29
- })
30
-
31
- const emit = defineEmits<{
32
- click: [event: MouseEvent]
33
- }>()
34
-
35
- const handleClick = (event: MouseEvent) => {
36
- if (props.disabled) {
37
- event.preventDefault()
38
- event.stopImmediatePropagation()
39
- } else {
40
- emit('click', event)
41
- }
42
- }
43
- </script>
44
-
45
- <style scoped>
46
- .btn {
47
- padding: 0.5rem 1rem;
48
- border: none;
49
- border-radius: 0.25rem;
50
- font-size: 1rem;
51
- font-weight: 500;
52
- cursor: pointer;
53
- transition: all 0.2s ease-in-out;
54
- display: inline-flex;
55
- align-items: center;
56
- justify-content: center;
57
- text-decoration: none;
58
- }
59
-
60
- .btn:hover:not(.btn--disabled) {
61
- transform: translateY(-1px);
62
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
63
- }
64
-
65
- .btn--primary {
66
- background-color: #3b82f6;
67
- color: white;
68
- }
69
-
70
- .btn--primary:hover:not(.btn--disabled) {
71
- background-color: #2563eb;
72
- }
73
-
74
- .btn--secondary {
75
- background-color: #6b7280;
76
- color: white;
77
- }
78
-
79
- .btn--secondary:hover:not(.btn--disabled) {
80
- background-color: #4b5563;
81
- }
82
-
83
- .btn--danger {
84
- background-color: #ef4444;
85
- color: white;
86
- }
87
-
88
- .btn--danger:hover:not(.btn--disabled) {
89
- background-color: #dc2626;
90
- }
91
-
92
- .btn--disabled {
93
- opacity: 0.5;
94
- cursor: not-allowed;
95
- pointer-events: none;
96
- }
97
-
98
- .sr-only {
99
- position: absolute;
100
- width: 1px;
101
- height: 1px;
102
- padding: 0;
103
- overflow: hidden;
104
- clip: rect(0, 0, 0, 0);
105
- white-space: nowrap;
106
- border: 0;
107
- }
108
- </style>
@@ -1,4 +0,0 @@
1
- export interface ButtonProps {
2
- variant?: 'primary' | 'secondary' | 'danger'
3
- disabled?: boolean
4
- }
@@ -1,261 +0,0 @@
1
- import type { Meta, StoryObj } from '@storybook/vue3'
2
- import { ref } from 'vue'
3
- import Calendar from './Calendar.vue'
4
-
5
- const meta = {
6
- title: 'Components/Calendar',
7
- component: Calendar,
8
- tags: ['autodocs'],
9
- argTypes: {
10
- mode: {
11
- control: 'select',
12
- options: ['single', 'multiple', 'range']
13
- },
14
- firstDayOfWeek: {
15
- control: 'select',
16
- options: [0, 1, 2, 3, 4, 5, 6],
17
- description: '0 = Sunday, 1 = Monday, etc.'
18
- }
19
- }
20
- } satisfies Meta<typeof Calendar>
21
-
22
- export default meta
23
- type Story = StoryObj<typeof meta>
24
-
25
- export const SingleDate: Story = {
26
- render: (args: any) => ({
27
- components: { Calendar },
28
- setup() {
29
- const selectedDate = ref<string>('')
30
- return { args, selectedDate }
31
- },
32
- template: `
33
- <div>
34
- <Calendar v-bind="args" v-model="selectedDate" />
35
- <div style="margin-top: 1rem; padding: 1rem; background: #f3f4f6; border-radius: 0.5rem;">
36
- <strong>Selected Date:</strong> {{ selectedDate || 'None' }}
37
- </div>
38
- </div>
39
- `
40
- }),
41
- args: {
42
- mode: 'single'
43
- }
44
- }
45
-
46
- export const MultipleDates: Story = {
47
- render: (args: any) => ({
48
- components: { Calendar },
49
- setup() {
50
- const selectedDates = ref<string[]>([])
51
- return { args, selectedDates }
52
- },
53
- template: `
54
- <div>
55
- <Calendar v-bind="args" v-model="selectedDates" />
56
- <div style="margin-top: 1rem; padding: 1rem; background: #f3f4f6; border-radius: 0.5rem;">
57
- <strong>Selected Dates:</strong>
58
- <ul v-if="selectedDates.length" style="margin: 0.5rem 0 0 0; padding-left: 1.5rem;">
59
- <li v-for="date in selectedDates" :key="date">{{ date }}</li>
60
- </ul>
61
- <span v-else> None</span>
62
- </div>
63
- </div>
64
- `
65
- }),
66
- args: {
67
- mode: 'multiple'
68
- }
69
- }
70
-
71
- export const DateRange: Story = {
72
- render: (args: any) => ({
73
- components: { Calendar },
74
- setup() {
75
- const dateRange = ref<{ start: string; end: string }>({ start: '', end: '' })
76
- return { args, dateRange }
77
- },
78
- template: `
79
- <div>
80
- <Calendar v-bind="args" v-model="dateRange" />
81
- <div style="margin-top: 1rem; padding: 1rem; background: #f3f4f6; border-radius: 0.5rem;">
82
- <strong>Date Range:</strong><br />
83
- Start: {{ dateRange.start || 'Not set' }}<br />
84
- End: {{ dateRange.end || 'Not set' }}
85
- </div>
86
- </div>
87
- `
88
- }),
89
- args: {
90
- mode: 'range'
91
- }
92
- }
93
-
94
- export const WithMinMaxDates: Story = {
95
- render: (args: any) => ({
96
- components: { Calendar },
97
- setup() {
98
- const selectedDate = ref<string>('')
99
- const today = new Date()
100
- const minDate = new Date(today)
101
- minDate.setDate(today.getDate() - 7)
102
- const maxDate = new Date(today)
103
- maxDate.setDate(today.getDate() + 14)
104
-
105
- return {
106
- args: {
107
- ...args,
108
- minDate: minDate.toISOString().split('T')[0],
109
- maxDate: maxDate.toISOString().split('T')[0]
110
- },
111
- selectedDate
112
- }
113
- },
114
- template: `
115
- <div>
116
- <Calendar v-bind="args" v-model="selectedDate" />
117
- <div style="margin-top: 1rem; padding: 1rem; background: #f3f4f6; border-radius: 0.5rem;">
118
- <strong>Selected Date:</strong> {{ selectedDate || 'None' }}<br />
119
- <em style="font-size: 0.875rem; color: #6b7280;">Min: {{ args.minDate }} | Max: {{ args.maxDate }}</em>
120
- </div>
121
- </div>
122
- `
123
- }),
124
- args: {
125
- mode: 'single'
126
- }
127
- }
128
-
129
- export const WithDisabledDates: Story = {
130
- render: (args: any) => ({
131
- components: { Calendar },
132
- setup() {
133
- const selectedDate = ref<string>('')
134
- const today = new Date()
135
- const disabledDates = [
136
- new Date(today.getFullYear(), today.getMonth(), 10).toISOString().split('T')[0],
137
- new Date(today.getFullYear(), today.getMonth(), 15).toISOString().split('T')[0],
138
- new Date(today.getFullYear(), today.getMonth(), 20).toISOString().split('T')[0],
139
- new Date(today.getFullYear(), today.getMonth(), 25).toISOString().split('T')[0]
140
- ]
141
-
142
- return {
143
- args: {
144
- ...args,
145
- disabledDates
146
- },
147
- selectedDate
148
- }
149
- },
150
- template: `
151
- <div>
152
- <Calendar v-bind="args" v-model="selectedDate" />
153
- <div style="margin-top: 1rem; padding: 1rem; background: #f3f4f6; border-radius: 0.5rem;">
154
- <strong>Selected Date:</strong> {{ selectedDate || 'None' }}<br />
155
- <em style="font-size: 0.875rem; color: #6b7280;">Disabled: 10th, 15th, 20th, 25th of current month</em>
156
- </div>
157
- </div>
158
- `
159
- }),
160
- args: {
161
- mode: 'single'
162
- }
163
- }
164
-
165
- export const MondayFirstDay: Story = {
166
- render: (args: any) => ({
167
- components: { Calendar },
168
- setup() {
169
- const selectedDate = ref<string>('')
170
- return { args, selectedDate }
171
- },
172
- template: `
173
- <div>
174
- <Calendar v-bind="args" v-model="selectedDate" />
175
- <div style="margin-top: 1rem; padding: 1rem; background: #f3f4f6; border-radius: 0.5rem;">
176
- <strong>Selected Date:</strong> {{ selectedDate || 'None' }}<br />
177
- <em style="font-size: 0.875rem; color: #6b7280;">Week starts on Monday</em>
178
- </div>
179
- </div>
180
- `
181
- }),
182
- args: {
183
- mode: 'single',
184
- firstDayOfWeek: 1
185
- }
186
- }
187
-
188
- export const BookingInterface: Story = {
189
- render: (args: any) => ({
190
- components: { Calendar },
191
- setup() {
192
- const dateRange = ref<{ start: string; end: string }>({ start: '', end: '' })
193
-
194
- const calculateNights = () => {
195
- if (!dateRange.value.start || !dateRange.value.end) return 0
196
- const start = new Date(dateRange.value.start)
197
- const end = new Date(dateRange.value.end)
198
- const diff = Math.abs(end.getTime() - start.getTime())
199
- return Math.ceil(diff / (1000 * 60 * 60 * 24))
200
- }
201
-
202
- return { args, dateRange, calculateNights }
203
- },
204
- template: `
205
- <div style="max-width: 600px;">
206
- <div style="display: flex; gap: 1rem; margin-bottom: 1rem;">
207
- <div style="flex: 1; padding: 1rem; background: white; border: 1px solid #e5e7eb; border-radius: 0.5rem;">
208
- <div style="font-size: 0.875rem; color: #6b7280; margin-bottom: 0.25rem;">Check-in</div>
209
- <div style="font-weight: 600;">{{ dateRange.start || 'Select date' }}</div>
210
- </div>
211
- <div style="flex: 1; padding: 1rem; background: white; border: 1px solid #e5e7eb; border-radius: 0.5rem;">
212
- <div style="font-size: 0.875rem; color: #6b7280; margin-bottom: 0.25rem;">Check-out</div>
213
- <div style="font-weight: 600;">{{ dateRange.end || 'Select date' }}</div>
214
- </div>
215
- </div>
216
-
217
- <Calendar v-bind="args" v-model="dateRange" />
218
-
219
- <div v-if="dateRange.start && dateRange.end" style="margin-top: 1rem; padding: 1rem; background: #eef2ff; border-radius: 0.5rem; color: #667eea;">
220
- <strong>{{ calculateNights() }} night{{ calculateNights() !== 1 ? 's' : '' }}</strong> selected
221
- </div>
222
- </div>
223
- `
224
- }),
225
- args: {
226
- mode: 'range'
227
- }
228
- }
229
-
230
- export const MultipleCalendars: Story = {
231
- render: (args: any) => ({
232
- components: { Calendar },
233
- setup() {
234
- const departureDate = ref<string>('')
235
- const returnDate = ref<string>('')
236
- return { args, departureDate, returnDate }
237
- },
238
- template: `
239
- <div style="display: flex; gap: 2rem; flex-wrap: wrap;">
240
- <div>
241
- <h3 style="margin: 0 0 1rem 0;">Departure</h3>
242
- <Calendar v-bind="args" v-model="departureDate" />
243
- <div style="margin-top: 0.5rem; font-size: 0.875rem; color: #6b7280;">
244
- {{ departureDate || 'No date selected' }}
245
- </div>
246
- </div>
247
-
248
- <div>
249
- <h3 style="margin: 0 0 1rem 0;">Return</h3>
250
- <Calendar v-bind="args" v-model="returnDate" />
251
- <div style="margin-top: 0.5rem; font-size: 0.875rem; color: #6b7280;">
252
- {{ returnDate || 'No date selected' }}
253
- </div>
254
- </div>
255
- </div>
256
- `
257
- }),
258
- args: {
259
- mode: 'single'
260
- }
261
- }
@@ -1,119 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { mount } from '@vue/test-utils'
3
- import Calendar from './Calendar.vue'
4
-
5
- describe('Calendar', () => {
6
- it('renders correctly', () => {
7
- const wrapper = mount(Calendar)
8
- expect(wrapper.find('.calendar').exists()).toBe(true)
9
- })
10
-
11
- it('displays correct month and year', () => {
12
- const wrapper = mount(Calendar)
13
- const today = new Date()
14
- const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
15
-
16
- expect(wrapper.text()).toContain(monthNames[today.getMonth()])
17
- expect(wrapper.text()).toContain(today.getFullYear().toString())
18
- })
19
-
20
- it('navigates to previous month', async () => {
21
- const wrapper = mount(Calendar)
22
- const navButtons = wrapper.findAll('.calendar-nav')
23
-
24
- await navButtons[0].trigger('click')
25
-
26
- // Calendar should show previous month
27
- expect(wrapper.find('.calendar-month-btn').exists()).toBe(true)
28
- })
29
-
30
- it('navigates to next month', async () => {
31
- const wrapper = mount(Calendar)
32
- const navButtons = wrapper.findAll('.calendar-nav')
33
-
34
- await navButtons[1].trigger('click')
35
-
36
- // Calendar should show next month
37
- expect(wrapper.find('.calendar-month-btn').exists()).toBe(true)
38
- })
39
-
40
- it('selects a date in single mode', async () => {
41
- const wrapper = mount(Calendar, {
42
- props: {
43
- mode: 'single'
44
- }
45
- })
46
-
47
- const dayButtons = wrapper.findAll('.calendar-day')
48
- const currentMonthDay = dayButtons.find(btn => !btn.classes().includes('calendar-day--other-month'))
49
-
50
- if (currentMonthDay) {
51
- await currentMonthDay.trigger('click')
52
- expect(wrapper.emitted('update:modelValue')).toBeTruthy()
53
- }
54
- })
55
-
56
- it('highlights today', () => {
57
- const wrapper = mount(Calendar)
58
- const todayButton = wrapper.find('.calendar-day--today')
59
- expect(todayButton.exists()).toBe(true)
60
- })
61
-
62
- it('respects minDate constraint', () => {
63
- const today = new Date()
64
- const tomorrow = new Date(today)
65
- tomorrow.setDate(today.getDate() + 1)
66
-
67
- const wrapper = mount(Calendar, {
68
- props: {
69
- minDate: tomorrow.toISOString().split('T')[0]
70
- }
71
- })
72
-
73
- const disabledDays = wrapper.findAll('.calendar-day--disabled')
74
- expect(disabledDays.length).toBeGreaterThan(0)
75
- })
76
-
77
- it('respects maxDate constraint', () => {
78
- const yesterday = new Date()
79
- yesterday.setDate(yesterday.getDate() - 1)
80
-
81
- const wrapper = mount(Calendar, {
82
- props: {
83
- maxDate: yesterday.toISOString().split('T')[0]
84
- }
85
- })
86
-
87
- const disabledDays = wrapper.findAll('.calendar-day--disabled')
88
- expect(disabledDays.length).toBeGreaterThan(0)
89
- })
90
-
91
- it('shows month picker when month button clicked', async () => {
92
- const wrapper = mount(Calendar)
93
- const monthButton = wrapper.find('.calendar-month-btn')
94
-
95
- await monthButton.trigger('click')
96
-
97
- expect(wrapper.find('.calendar-picker').exists()).toBe(true)
98
- })
99
-
100
- it('shows year picker when year button clicked', async () => {
101
- const wrapper = mount(Calendar)
102
- const yearButton = wrapper.find('.calendar-year-btn')
103
-
104
- await yearButton.trigger('click')
105
-
106
- expect(wrapper.find('.calendar-picker').exists()).toBe(true)
107
- })
108
-
109
- it('displays correct first day of week', () => {
110
- const wrapper = mount(Calendar, {
111
- props: {
112
- firstDayOfWeek: 1 // Monday
113
- }
114
- })
115
-
116
- const firstWeekday = wrapper.find('.calendar-weekday')
117
- expect(firstWeekday.text()).toBe('Mon')
118
- })
119
- })