@nexxtmove/ui 0.1.31 → 0.1.33
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/index.d.ts +124 -17
- package/dist/index.js +1079 -980
- package/dist/nuxt.js +8 -7
- package/package.json +1 -1
- package/src/components/Calendar/_CalendarDayView.vue +1 -1
- package/src/components/Chip/Chip.vue +9 -4
- package/src/components/Pagination/Pagination.vue +93 -0
- package/src/components/Table/Table.vue +2 -2
- package/src/components.json +1 -0
- package/src/index.ts +1 -0
- package/src/components/AnimatedNumber/AnimatedNumber.stories.ts +0 -15
- package/src/components/AnimatedNumber/AnimatedNumber.test.ts +0 -56
- package/src/components/Avatar/Avatar.stories.ts +0 -28
- package/src/components/Avatar/Avatar.test.ts +0 -55
- package/src/components/Button/Button.stories.ts +0 -212
- package/src/components/Button/Button.test.ts +0 -318
- package/src/components/Calendar/Calendar.stories.ts +0 -91
- package/src/components/Calendar/Calendar.test.ts +0 -279
- package/src/components/Calendar/_CalendarDayView.test.ts +0 -104
- package/src/components/Calendar/_CalendarHeader.test.ts +0 -86
- package/src/components/Calendar/_CalendarMonthView.test.ts +0 -52
- package/src/components/Calendar/_CalendarYearView.test.ts +0 -56
- package/src/components/Checkbox/Checkbox.stories.ts +0 -35
- package/src/components/Checkbox/Checkbox.test.ts +0 -55
- package/src/components/Chip/Chip.stories.ts +0 -42
- package/src/components/Chip/Chip.test.ts +0 -51
- package/src/components/CustomerJourneyTile/CustomerJourneyTile.stories.ts +0 -86
- package/src/components/CustomerJourneyTile/CustomerJourneyTile.test.ts +0 -178
- package/src/components/DatePicker/DatePicker.stories.ts +0 -149
- package/src/components/DatePicker/DatePicker.test.ts +0 -191
- package/src/components/DropdownButton/DropdownButton.stories.ts +0 -68
- package/src/components/DropdownButton/DropdownButton.test.ts +0 -124
- package/src/components/Header/Header.stories.ts +0 -54
- package/src/components/Header/Header.test.ts +0 -183
- package/src/components/Icon/Icon.stories.ts +0 -50
- package/src/components/Icon/Icon.test.ts +0 -73
- package/src/components/InfoBlock/InfoBlock.stories.ts +0 -90
- package/src/components/InfoBlock/InfoBlock.test.ts +0 -101
- package/src/components/ProgressBar/ProgressBar.stories.ts +0 -30
- package/src/components/ProgressBar/ProgressBar.test.ts +0 -314
- package/src/components/SocialIcons/SocialIcons.stories.ts +0 -34
- package/src/components/SocialIcons/SocialIcons.test.ts +0 -58
- package/src/components/SocialMediaCustomTemplate/SocialMediaCustomTemplate.stories.ts +0 -11
- package/src/components/SocialMediaCustomTemplate/SocialMediaCustomTemplate.test.ts +0 -131
- package/src/components/SocialMediaTemplate/SocialMediaTemplate.stories.ts +0 -71
- package/src/components/SocialMediaTemplate/SocialMediaTemplate.test.ts +0 -466
- package/src/components/SocialMediaType/SocialMediaType.stories.ts +0 -43
- package/src/components/SocialMediaType/SocialMediaType.test.ts +0 -126
- package/src/components/StepperHeader/StepperHeader.stories.ts +0 -47
- package/src/components/StepperHeader/StepperHeader.test.ts +0 -244
- package/src/components/Table/Table.stories.ts +0 -283
- package/src/components/Table/Table.test.ts +0 -220
- package/src/components/TimelineEvent/TimelineEvent.stories.ts +0 -291
- package/src/components/TimelineEvent/TimelineEvent.test.ts +0 -166
- package/src/components/TimelinePhaseblock/TimelinePhaseblock.stories.ts +0 -198
- package/src/components/TimelinePhaseblock/TimelinePhaseblock.test.ts +0 -283
- package/src/components/Tooltip/Tooltip.stories.ts +0 -146
- package/src/components/Tooltip/Tooltip.test.ts +0 -122
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { mount } from '@vue/test-utils'
|
|
3
|
-
import Button from './Button.vue'
|
|
4
|
-
|
|
5
|
-
describe('Button', () => {
|
|
6
|
-
it('renders slot content', () => {
|
|
7
|
-
const wrapper = mount(Button, {
|
|
8
|
-
slots: {
|
|
9
|
-
default: 'Click me',
|
|
10
|
-
},
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
expect(wrapper.text()).toBe('Click me')
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it('renders complex slot content', () => {
|
|
17
|
-
const wrapper = mount(Button, {
|
|
18
|
-
slots: {
|
|
19
|
-
default: '<span>Complex</span> <span>Text</span>',
|
|
20
|
-
},
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
expect(wrapper.html()).toContain('<span>Complex</span>')
|
|
24
|
-
expect(wrapper.html()).toContain('<span>Text</span>')
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('applies primary variant by default', () => {
|
|
28
|
-
const wrapper = mount(Button, {
|
|
29
|
-
slots: {
|
|
30
|
-
default: 'Click me',
|
|
31
|
-
},
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
expect(wrapper.classes()).toContain('button-primary')
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('applies secondary variant when specified', () => {
|
|
38
|
-
const wrapper = mount(Button, {
|
|
39
|
-
props: {
|
|
40
|
-
variant: 'secondary',
|
|
41
|
-
},
|
|
42
|
-
slots: {
|
|
43
|
-
default: 'Click me',
|
|
44
|
-
},
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
expect(wrapper.classes()).toContain('button-secondary')
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('applies success variant when specified', () => {
|
|
51
|
-
const wrapper = mount(Button, {
|
|
52
|
-
props: {
|
|
53
|
-
variant: 'success',
|
|
54
|
-
},
|
|
55
|
-
slots: {
|
|
56
|
-
default: 'Click me',
|
|
57
|
-
},
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
expect(wrapper.classes()).toContain('button-success')
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
it('applies danger variant when specified', () => {
|
|
64
|
-
const wrapper = mount(Button, {
|
|
65
|
-
props: {
|
|
66
|
-
variant: 'danger',
|
|
67
|
-
},
|
|
68
|
-
slots: {
|
|
69
|
-
default: 'Click me',
|
|
70
|
-
},
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
expect(wrapper.classes()).toContain('button-danger')
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('applies link variant when specified', () => {
|
|
77
|
-
const wrapper = mount(Button, {
|
|
78
|
-
props: {
|
|
79
|
-
variant: 'link',
|
|
80
|
-
},
|
|
81
|
-
slots: {
|
|
82
|
-
default: 'Click me',
|
|
83
|
-
},
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
expect(wrapper.classes()).toContain('button-link')
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
it('applies link variant special classes', () => {
|
|
90
|
-
const wrapper = mount(Button, {
|
|
91
|
-
props: {
|
|
92
|
-
variant: 'link',
|
|
93
|
-
},
|
|
94
|
-
slots: {
|
|
95
|
-
default: 'Click me',
|
|
96
|
-
},
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
const span = wrapper.find('span')
|
|
100
|
-
expect(span.classes()).toContain('body-link')
|
|
101
|
-
expect(span.classes()).toContain('underline-offset-1')
|
|
102
|
-
expect(span.classes()).toContain('hover:underline-offset-2')
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
it('does not apply hover underline offset when link is disabled', () => {
|
|
106
|
-
const wrapper = mount(Button, {
|
|
107
|
-
props: {
|
|
108
|
-
variant: 'link',
|
|
109
|
-
disabled: true,
|
|
110
|
-
},
|
|
111
|
-
slots: {
|
|
112
|
-
default: 'Click me',
|
|
113
|
-
},
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
const span = wrapper.find('span')
|
|
117
|
-
expect(span.classes()).not.toContain('hover:underline-offset-2')
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('renders icon when icon prop is provided', () => {
|
|
121
|
-
const wrapper = mount(Button, {
|
|
122
|
-
props: {
|
|
123
|
-
icon: 'star',
|
|
124
|
-
},
|
|
125
|
-
slots: {
|
|
126
|
-
default: 'Click me',
|
|
127
|
-
},
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
const icon = wrapper.find('i')
|
|
131
|
-
expect(icon.exists()).toBe(true)
|
|
132
|
-
expect(icon.classes()).toContain('fa-light')
|
|
133
|
-
expect(icon.classes()).toContain('fa-star')
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
it('does not render icon when icon prop is not provided', () => {
|
|
137
|
-
const wrapper = mount(Button, {
|
|
138
|
-
slots: {
|
|
139
|
-
default: 'Click me',
|
|
140
|
-
},
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
const icon = wrapper.find('i')
|
|
144
|
-
expect(icon.exists()).toBe(false)
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('positions icon on the right when iconRight is true', () => {
|
|
148
|
-
const wrapper = mount(Button, {
|
|
149
|
-
props: {
|
|
150
|
-
icon: 'arrow-right',
|
|
151
|
-
iconRight: true,
|
|
152
|
-
},
|
|
153
|
-
slots: {
|
|
154
|
-
default: 'Next',
|
|
155
|
-
},
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
const icon = wrapper.find('i')
|
|
159
|
-
expect(icon.classes()).toContain('order-last')
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it('positions icon on the left by default', () => {
|
|
163
|
-
const wrapper = mount(Button, {
|
|
164
|
-
props: {
|
|
165
|
-
icon: 'arrow-right',
|
|
166
|
-
},
|
|
167
|
-
slots: {
|
|
168
|
-
default: 'Back',
|
|
169
|
-
},
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
const icon = wrapper.find('i')
|
|
173
|
-
expect(icon.classes()).not.toContain('order-last')
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
it('renders as button type="button"', () => {
|
|
177
|
-
const wrapper = mount(Button, {
|
|
178
|
-
slots: {
|
|
179
|
-
default: 'Click me',
|
|
180
|
-
},
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
expect(wrapper.element.tagName).toBe('BUTTON')
|
|
184
|
-
expect(wrapper.attributes('type')).toBe('button')
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
it('disables button when disabled prop is true', () => {
|
|
188
|
-
const wrapper = mount(Button, {
|
|
189
|
-
props: {
|
|
190
|
-
disabled: true,
|
|
191
|
-
},
|
|
192
|
-
slots: {
|
|
193
|
-
default: 'Click me',
|
|
194
|
-
},
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
expect(wrapper.attributes('disabled')).toBeDefined()
|
|
198
|
-
expect(wrapper.classes()).toContain('disabled:cursor-not-allowed')
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
it('applies disabled styles for primary variant', () => {
|
|
202
|
-
const wrapper = mount(Button, {
|
|
203
|
-
props: {
|
|
204
|
-
variant: 'primary',
|
|
205
|
-
disabled: true,
|
|
206
|
-
},
|
|
207
|
-
slots: {
|
|
208
|
-
default: 'Click me',
|
|
209
|
-
},
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
expect(wrapper.attributes()).toHaveProperty('disabled')
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
it('hides icon and slot content when loading', () => {
|
|
216
|
-
const wrapper = mount(Button, {
|
|
217
|
-
props: {
|
|
218
|
-
loading: true,
|
|
219
|
-
icon: 'star',
|
|
220
|
-
},
|
|
221
|
-
slots: {
|
|
222
|
-
default: 'Submit',
|
|
223
|
-
},
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
const starIcon = wrapper.find('.fa-star')
|
|
227
|
-
expect(starIcon.classes()).toContain('opacity-0')
|
|
228
|
-
|
|
229
|
-
const label = wrapper.find('span')
|
|
230
|
-
expect(label.classes()).toContain('opacity-0')
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
it('removes hover effects when loading or disabled in link variant', async () => {
|
|
234
|
-
const wrapper = mount(Button, {
|
|
235
|
-
props: {
|
|
236
|
-
variant: 'link',
|
|
237
|
-
loading: true,
|
|
238
|
-
},
|
|
239
|
-
slots: {
|
|
240
|
-
default: 'Link',
|
|
241
|
-
},
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
expect(wrapper.find('span').classes()).not.toContain('hover:underline-offset-2')
|
|
245
|
-
|
|
246
|
-
await wrapper.setProps({ loading: false, disabled: true })
|
|
247
|
-
expect(wrapper.find('span').classes()).not.toContain('hover:underline-offset-2')
|
|
248
|
-
|
|
249
|
-
await wrapper.setProps({ disabled: false })
|
|
250
|
-
expect(wrapper.find('span').classes()).toContain('hover:underline-offset-2')
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
it('renders icon-only button without padding', () => {
|
|
254
|
-
const wrapper = mount(Button, {
|
|
255
|
-
props: {
|
|
256
|
-
icon: 'star',
|
|
257
|
-
},
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
expect(wrapper.classes()).toContain('w-10')
|
|
261
|
-
expect(wrapper.classes()).not.toContain('px-4')
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
it('displays spinner icon when loading is true', () => {
|
|
265
|
-
const wrapper = mount(Button, {
|
|
266
|
-
props: {
|
|
267
|
-
loading: true,
|
|
268
|
-
},
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
const icon = wrapper.find('i')
|
|
272
|
-
expect(icon.exists()).toBe(true)
|
|
273
|
-
expect(icon.classes()).toContain('fa-spinner-third')
|
|
274
|
-
expect(icon.classes()).toContain('fa-spin')
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
it('disables button when loading is true', () => {
|
|
278
|
-
const wrapper = mount(Button, {
|
|
279
|
-
props: {
|
|
280
|
-
loading: true,
|
|
281
|
-
},
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
expect(wrapper.classes()).toContain('pointer-events-none')
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
it('set the button type prop', async () => {
|
|
288
|
-
const wrapper = mount(Button)
|
|
289
|
-
|
|
290
|
-
expect(wrapper.attributes('type')).toBe('button')
|
|
291
|
-
|
|
292
|
-
await wrapper.setProps({ buttonType: 'submit' })
|
|
293
|
-
expect(wrapper.attributes('type')).toBe('submit')
|
|
294
|
-
|
|
295
|
-
await wrapper.setProps({ buttonType: 'reset' })
|
|
296
|
-
expect(wrapper.attributes('type')).toBe('reset')
|
|
297
|
-
})
|
|
298
|
-
|
|
299
|
-
it('no animated span whitout default slot', async () => {
|
|
300
|
-
const wrapper = mount(Button)
|
|
301
|
-
|
|
302
|
-
const slotSpanWrapper = wrapper.find('span')
|
|
303
|
-
|
|
304
|
-
expect(slotSpanWrapper.exists()).toBe(false)
|
|
305
|
-
})
|
|
306
|
-
|
|
307
|
-
it('has animated span with default slot', async () => {
|
|
308
|
-
const wrapper = mount(Button, {
|
|
309
|
-
slots: {
|
|
310
|
-
default: 'text',
|
|
311
|
-
},
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
const slotSpanWrapper = wrapper.find('span')
|
|
315
|
-
|
|
316
|
-
expect(slotSpanWrapper.exists()).toBe(true)
|
|
317
|
-
})
|
|
318
|
-
})
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
2
|
-
import { ref } from 'vue'
|
|
3
|
-
import { enUS } from 'date-fns/locale'
|
|
4
|
-
import Calendar from './Calendar.vue'
|
|
5
|
-
export default {
|
|
6
|
-
title: 'Components/Atoms/Calendar',
|
|
7
|
-
component: Calendar,
|
|
8
|
-
parameters: {
|
|
9
|
-
design: {
|
|
10
|
-
type: 'figma',
|
|
11
|
-
url: 'https://www.figma.com/design/CdbFJ7qUga6mtjagcPDvYK/Design-System?node-id=713-720&t=CbHvOQfEMIr7M5mO-4',
|
|
12
|
-
},
|
|
13
|
-
},
|
|
14
|
-
} satisfies Meta<typeof Calendar>
|
|
15
|
-
|
|
16
|
-
type Story = StoryObj<typeof Calendar>
|
|
17
|
-
|
|
18
|
-
export const Default: Story = {
|
|
19
|
-
args: {},
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const WithSelectedDate: Story = {
|
|
23
|
-
args: {
|
|
24
|
-
modelValue: new Date(),
|
|
25
|
-
},
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const WithMarkedDates: Story = {
|
|
29
|
-
args: {
|
|
30
|
-
markedDates: [
|
|
31
|
-
new Date(new Date().getFullYear(), new Date().getMonth(), 5),
|
|
32
|
-
new Date(new Date().getFullYear(), new Date().getMonth(), 12),
|
|
33
|
-
new Date(new Date().getFullYear(), new Date().getMonth(), 20),
|
|
34
|
-
],
|
|
35
|
-
},
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export const WithDisabledDates: Story = {
|
|
39
|
-
args: {
|
|
40
|
-
disabledDates: [
|
|
41
|
-
new Date(new Date().getFullYear(), new Date().getMonth(), 10),
|
|
42
|
-
new Date(new Date().getFullYear(), new Date().getMonth(), 11),
|
|
43
|
-
new Date(new Date().getFullYear(), new Date().getMonth(), 12),
|
|
44
|
-
],
|
|
45
|
-
},
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export const WithDateRange: Story = {
|
|
49
|
-
args: {
|
|
50
|
-
minDate: new Date(new Date().getFullYear(), new Date().getMonth(), 5),
|
|
51
|
-
maxDate: new Date(new Date().getFullYear(), new Date().getMonth() + 1, 20),
|
|
52
|
-
},
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export const WithMonthYearPicker: Story = {
|
|
56
|
-
args: {
|
|
57
|
-
monthYearPicker: true,
|
|
58
|
-
},
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export const EnglishLocale: Story = {
|
|
62
|
-
args: {
|
|
63
|
-
locale: enUS,
|
|
64
|
-
},
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export const Playground: Story = {
|
|
68
|
-
render: (args) => ({
|
|
69
|
-
components: { Calendar },
|
|
70
|
-
setup() {
|
|
71
|
-
const selected = ref<Date | null>(null)
|
|
72
|
-
return { args, selected }
|
|
73
|
-
},
|
|
74
|
-
template: `
|
|
75
|
-
<Calendar
|
|
76
|
-
v-bind="args"
|
|
77
|
-
v-model="selected"
|
|
78
|
-
/>
|
|
79
|
-
<p v-if="selected" style="margin-top: 12px; font-size: 13px; color: #555; display: block;">
|
|
80
|
-
Geselecteerd: {{ selected.toLocaleDateString('nl-NL') }}
|
|
81
|
-
</p>
|
|
82
|
-
`,
|
|
83
|
-
}),
|
|
84
|
-
args: {
|
|
85
|
-
monthYearPicker: true,
|
|
86
|
-
markedDates: [
|
|
87
|
-
new Date(new Date().getFullYear(), new Date().getMonth(), 8),
|
|
88
|
-
new Date(new Date().getFullYear(), new Date().getMonth(), 15),
|
|
89
|
-
],
|
|
90
|
-
},
|
|
91
|
-
}
|
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
-
import { mount } from '@vue/test-utils'
|
|
3
|
-
import { nextTick } from 'vue'
|
|
4
|
-
import Calendar from './Calendar.vue'
|
|
5
|
-
|
|
6
|
-
// Mock today's date for consistent testing
|
|
7
|
-
const MOCK_TODAY = new Date(Date.UTC(2026, 2, 20)) // March 20, 2026
|
|
8
|
-
vi.useFakeTimers()
|
|
9
|
-
vi.setSystemTime(MOCK_TODAY)
|
|
10
|
-
|
|
11
|
-
// Fixed reference date used across tests
|
|
12
|
-
const YEAR = 2026
|
|
13
|
-
const MONTH = 2 // March (0-indexed)
|
|
14
|
-
const TODAY = new Date(Date.UTC(YEAR, MONTH, 20))
|
|
15
|
-
|
|
16
|
-
const mountCalendar = (props = {}) => mount(Calendar, { props })
|
|
17
|
-
|
|
18
|
-
describe('Calendar', () => {
|
|
19
|
-
// ── Rendering ─────────────────────────────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
it('renders the calendar widget', () => {
|
|
22
|
-
const wrapper = mountCalendar({ modelValue: TODAY })
|
|
23
|
-
expect(wrapper.find('[class*="rounded-xl"]').exists()).toBe(true)
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('shows 7 weekday header columns', () => {
|
|
27
|
-
const wrapper = mountCalendar({ modelValue: TODAY })
|
|
28
|
-
const headers = wrapper.findAll('[class*="grid-cols-7"] > div').slice(0, 7)
|
|
29
|
-
expect(headers.length).toBe(7)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
// ── Day selection ──────────────────────────────────────────────────────────
|
|
33
|
-
|
|
34
|
-
it('emits update:modelValue and select when a day is clicked', async () => {
|
|
35
|
-
const wrapper = mountCalendar({ modelValue: TODAY })
|
|
36
|
-
|
|
37
|
-
const dayButtons = wrapper
|
|
38
|
-
.findAll('button[type="button"]')
|
|
39
|
-
.filter((b) => /^\d{1,2}$/.test(b.text().trim()))
|
|
40
|
-
await dayButtons[10]!.trigger('click')
|
|
41
|
-
await nextTick()
|
|
42
|
-
|
|
43
|
-
expect(wrapper.emitted('update:modelValue')).toBeTruthy()
|
|
44
|
-
expect(wrapper.emitted('update:modelValue')![0]![0]).toBeInstanceOf(Date)
|
|
45
|
-
expect(wrapper.emitted('select')).toBeTruthy()
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
// ── Month navigation ───────────────────────────────────────────────────────
|
|
49
|
-
|
|
50
|
-
it('navigates to next month', async () => {
|
|
51
|
-
const wrapper = mountCalendar({ modelValue: new Date(Date.UTC(YEAR, MONTH, 1)) })
|
|
52
|
-
|
|
53
|
-
const labelBefore = wrapper
|
|
54
|
-
.find('[class*="flex"][class*="items-center"][class*="gap-1"]')
|
|
55
|
-
.text()
|
|
56
|
-
const nextBtn = wrapper
|
|
57
|
-
.findAll('button[aria-label]')
|
|
58
|
-
.find((b) => b.attributes('aria-label') === 'Volgende maand')
|
|
59
|
-
await nextBtn!.trigger('click')
|
|
60
|
-
await nextTick()
|
|
61
|
-
|
|
62
|
-
const labelAfter = wrapper.find('[class*="flex"][class*="items-center"][class*="gap-1"]').text()
|
|
63
|
-
expect(labelAfter).not.toBe(labelBefore)
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('navigates to previous month', async () => {
|
|
67
|
-
const wrapper = mountCalendar({ modelValue: new Date(Date.UTC(YEAR, MONTH, 1)) })
|
|
68
|
-
|
|
69
|
-
const labelBefore = wrapper
|
|
70
|
-
.find('[class*="flex"][class*="items-center"][class*="gap-1"]')
|
|
71
|
-
.text()
|
|
72
|
-
const prevBtn = wrapper
|
|
73
|
-
.findAll('button[aria-label]')
|
|
74
|
-
.find((b) => b.attributes('aria-label') === 'Vorige maand')
|
|
75
|
-
await prevBtn!.trigger('click')
|
|
76
|
-
await nextTick()
|
|
77
|
-
|
|
78
|
-
const labelAfter = wrapper.find('[class*="flex"][class*="items-center"][class*="gap-1"]').text()
|
|
79
|
-
expect(labelAfter).not.toBe(labelBefore)
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
// ── Marked dates ───────────────────────────────────────────────────────────
|
|
83
|
-
|
|
84
|
-
it('renders a dot for marked dates', () => {
|
|
85
|
-
const markedDates = [new Date(Date.UTC(YEAR, MONTH, 10))]
|
|
86
|
-
const wrapper = mountCalendar({ modelValue: new Date(Date.UTC(YEAR, MONTH, 1)), markedDates })
|
|
87
|
-
|
|
88
|
-
expect(wrapper.find('[class*="rounded-full"][class*="bg-cornflower"]').exists()).toBe(true)
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
// ── Disabled dates ─────────────────────────────────────────────────────────
|
|
92
|
-
|
|
93
|
-
it('disables specified dates', () => {
|
|
94
|
-
const disabledDates = [new Date(Date.UTC(YEAR, MONTH, 15))]
|
|
95
|
-
const wrapper = mountCalendar({ modelValue: new Date(Date.UTC(YEAR, MONTH, 1)), disabledDates })
|
|
96
|
-
|
|
97
|
-
const disabled = wrapper
|
|
98
|
-
.findAll('button[disabled]')
|
|
99
|
-
.filter((b) => /^\d{1,2}$/.test(b.text().trim()))
|
|
100
|
-
expect(disabled.length).toBeGreaterThan(0)
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
it('does not emit when clicking a disabled date', async () => {
|
|
104
|
-
const disabledDates = [new Date(Date.UTC(YEAR, MONTH, 15))]
|
|
105
|
-
const wrapper = mountCalendar({ modelValue: new Date(Date.UTC(YEAR, MONTH, 1)), disabledDates })
|
|
106
|
-
|
|
107
|
-
const disabledBtn = wrapper
|
|
108
|
-
.findAll('button[disabled]')
|
|
109
|
-
.find((b) => /^\d{1,2}$/.test(b.text().trim()))
|
|
110
|
-
await disabledBtn?.trigger('click')
|
|
111
|
-
await nextTick()
|
|
112
|
-
|
|
113
|
-
expect(wrapper.emitted('update:modelValue')).toBeFalsy()
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
// ── minDate / maxDate ──────────────────────────────────────────────────────
|
|
117
|
-
|
|
118
|
-
it('disables dates before minDate', () => {
|
|
119
|
-
const minDate = new Date(Date.UTC(YEAR, MONTH, 10))
|
|
120
|
-
const wrapper = mountCalendar({ modelValue: new Date(Date.UTC(YEAR, MONTH, 15)), minDate })
|
|
121
|
-
|
|
122
|
-
const disabled = wrapper
|
|
123
|
-
.findAll('button[disabled]')
|
|
124
|
-
.filter((b) => /^\d{1,2}$/.test(b.text().trim()))
|
|
125
|
-
expect(disabled.length).toBeGreaterThan(0)
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
it('disables dates after maxDate', () => {
|
|
129
|
-
const maxDate = new Date(Date.UTC(YEAR, MONTH, 20))
|
|
130
|
-
const wrapper = mountCalendar({ modelValue: new Date(Date.UTC(YEAR, MONTH, 15)), maxDate })
|
|
131
|
-
|
|
132
|
-
const disabled = wrapper
|
|
133
|
-
.findAll('button[disabled]')
|
|
134
|
-
.filter((b) => /^\d{1,2}$/.test(b.text().trim()))
|
|
135
|
-
expect(disabled.length).toBeGreaterThan(0)
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
it('disables prev nav when already at minDate month', () => {
|
|
139
|
-
const minDate = new Date(Date.UTC(YEAR, MONTH, 1))
|
|
140
|
-
const wrapper = mountCalendar({ modelValue: new Date(Date.UTC(YEAR, MONTH, 15)), minDate })
|
|
141
|
-
|
|
142
|
-
const prevBtn = wrapper.find('button[aria-label="Vorige maand"]')
|
|
143
|
-
expect(prevBtn.attributes('disabled')).toBeDefined()
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('disables next nav when already at maxDate month', () => {
|
|
147
|
-
const maxDate = new Date(Date.UTC(YEAR, MONTH, 28))
|
|
148
|
-
const wrapper = mountCalendar({ modelValue: new Date(Date.UTC(YEAR, MONTH, 15)), maxDate })
|
|
149
|
-
|
|
150
|
-
const nextBtn = wrapper.find('button[aria-label="Volgende maand"]')
|
|
151
|
-
expect(nextBtn.attributes('disabled')).toBeDefined()
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
it('does not navigate prev when at minDate month', async () => {
|
|
155
|
-
const minDate = new Date(Date.UTC(YEAR, MONTH, 1))
|
|
156
|
-
const wrapper = mountCalendar({ modelValue: new Date(Date.UTC(YEAR, MONTH, 15)), minDate })
|
|
157
|
-
|
|
158
|
-
const prevBtn = wrapper.find('button[aria-label="Vorige maand"]')
|
|
159
|
-
await prevBtn.trigger('click')
|
|
160
|
-
await nextTick()
|
|
161
|
-
|
|
162
|
-
const label = wrapper.find('[class*="flex"][class*="items-center"][class*="gap-1"]').text()
|
|
163
|
-
expect(label).toContain('Mrt')
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
it('does not navigate next when at maxDate month', async () => {
|
|
167
|
-
const maxDate = new Date(Date.UTC(YEAR, MONTH, 28))
|
|
168
|
-
const wrapper = mountCalendar({ modelValue: new Date(Date.UTC(YEAR, MONTH, 15)), maxDate })
|
|
169
|
-
|
|
170
|
-
const nextBtn = wrapper.find('button[aria-label="Volgende maand"]')
|
|
171
|
-
await nextBtn.trigger('click')
|
|
172
|
-
await nextTick()
|
|
173
|
-
|
|
174
|
-
const label = wrapper.find('[class*="flex"][class*="items-center"][class*="gap-1"]').text()
|
|
175
|
-
expect(label).toContain('Mrt')
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
// ── monthYearPicker ────────────────────────────────────────────────────────
|
|
179
|
-
|
|
180
|
-
it('month name is not a button without monthYearPicker prop', () => {
|
|
181
|
-
const wrapper = mountCalendar({ modelValue: TODAY })
|
|
182
|
-
|
|
183
|
-
const header = wrapper.find('[class*="flex"][class*="items-center"][class*="gap-1"]')
|
|
184
|
-
expect(header.find('button').exists()).toBe(false)
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
it('month name is a button with monthYearPicker prop', () => {
|
|
188
|
-
const wrapper = mountCalendar({ modelValue: TODAY, monthYearPicker: true })
|
|
189
|
-
|
|
190
|
-
const header = wrapper.find('[class*="flex"][class*="items-center"][class*="gap-1"]')
|
|
191
|
-
expect(header.find('button').exists()).toBe(true)
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
it('opens month picker when month name is clicked', async () => {
|
|
195
|
-
const wrapper = mountCalendar({ modelValue: TODAY, monthYearPicker: true })
|
|
196
|
-
|
|
197
|
-
const header = wrapper.find('[class*="flex"][class*="items-center"][class*="gap-1"]')
|
|
198
|
-
const monthBtn = header.findAll('button').find((b) => b.text().includes('Mrt'))
|
|
199
|
-
await monthBtn!.trigger('click')
|
|
200
|
-
await nextTick()
|
|
201
|
-
|
|
202
|
-
// Month view uses grid-cols-3
|
|
203
|
-
expect(wrapper.find('[class*="grid-cols-3"]').exists()).toBe(true)
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
it('opens year picker when year is clicked', async () => {
|
|
207
|
-
const wrapper = mountCalendar({ modelValue: TODAY, monthYearPicker: true })
|
|
208
|
-
|
|
209
|
-
const header = wrapper.find('[class*="flex"][class*="items-center"][class*="gap-1"]')
|
|
210
|
-
const buttons = header.findAll('button')
|
|
211
|
-
await buttons[1]!.trigger('click')
|
|
212
|
-
await nextTick()
|
|
213
|
-
|
|
214
|
-
expect(wrapper.find('[class*="snap-y"]').exists()).toBe(true)
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
it('returns to days view after selecting a month', async () => {
|
|
218
|
-
const wrapper = mountCalendar({ modelValue: TODAY, monthYearPicker: true })
|
|
219
|
-
|
|
220
|
-
const header = wrapper.find('[class*="flex"][class*="items-center"][class*="gap-1"]')
|
|
221
|
-
await header.find('button').trigger('click')
|
|
222
|
-
await nextTick()
|
|
223
|
-
|
|
224
|
-
const monthButtons = wrapper.findAll('[class*="grid-cols-3"] button')
|
|
225
|
-
await monthButtons[0]!.trigger('click')
|
|
226
|
-
await nextTick()
|
|
227
|
-
|
|
228
|
-
expect(wrapper.find('[class*="grid-cols-7"]').exists()).toBe(true)
|
|
229
|
-
expect(wrapper.find('[class*="grid-cols-3"]').exists()).toBe(false)
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
it('returns to months view after selecting a year', async () => {
|
|
233
|
-
const wrapper = mountCalendar({ modelValue: TODAY, monthYearPicker: true })
|
|
234
|
-
|
|
235
|
-
const header = wrapper.find('[class*="flex"][class*="items-center"][class*="gap-1"]')
|
|
236
|
-
await header.findAll('button')[1]!.trigger('click')
|
|
237
|
-
await nextTick()
|
|
238
|
-
|
|
239
|
-
const yearButtons = wrapper.findAll('[class*="snap-center"]')
|
|
240
|
-
await yearButtons[0]!.trigger('click')
|
|
241
|
-
await nextTick()
|
|
242
|
-
|
|
243
|
-
expect(wrapper.find('[class*="grid-cols-3"]').exists()).toBe(true)
|
|
244
|
-
expect(wrapper.find('[class*="snap-y"]').exists()).toBe(false)
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
it('selects correct date from month picker', async () => {
|
|
248
|
-
const wrapper = mountCalendar({ modelValue: TODAY, monthYearPicker: true })
|
|
249
|
-
|
|
250
|
-
const header = wrapper.find('[class*="flex"][class*="items-center"][class*="gap-1"]')
|
|
251
|
-
await header.find('button').trigger('click')
|
|
252
|
-
await nextTick()
|
|
253
|
-
|
|
254
|
-
const monthButtons = wrapper.findAll('[class*="grid-cols-3"] button')
|
|
255
|
-
await monthButtons[0]?.trigger('click')
|
|
256
|
-
await nextTick()
|
|
257
|
-
|
|
258
|
-
const labelAfter = wrapper.find('[class*="cornflower-blue-600"]').text()
|
|
259
|
-
expect(labelAfter).toContain('Jan')
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
it('selects correct year from year drum', async () => {
|
|
263
|
-
const wrapper = mountCalendar({ modelValue: TODAY, monthYearPicker: true })
|
|
264
|
-
|
|
265
|
-
const header = wrapper.find('[class*="flex"][class*="items-center"][class*="gap-1"]')
|
|
266
|
-
const headerButtons = header.findAll('button')
|
|
267
|
-
await headerButtons[1]?.trigger('click')
|
|
268
|
-
await nextTick()
|
|
269
|
-
|
|
270
|
-
const yearButtons = wrapper.findAll('[class*="snap-center"]')
|
|
271
|
-
const targetYear = (YEAR - 1).toString()
|
|
272
|
-
const targetBtn = yearButtons.find((btn) => btn.text().includes(targetYear))
|
|
273
|
-
await targetBtn!.trigger('click')
|
|
274
|
-
await nextTick()
|
|
275
|
-
|
|
276
|
-
const labelAfter = wrapper.find('[class*="cornflower-blue-600"]').text()
|
|
277
|
-
expect(labelAfter).toContain(targetYear)
|
|
278
|
-
})
|
|
279
|
-
})
|