@nexxtmove/ui 2.5.0 → 2.7.0
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/components/editor/molecules/EditorBorder/EditorBorder.vue.d.ts +12 -0
- package/dist/components/editor/molecules/EditorColor/_ThemeRoleSwatches.vue.d.ts +16 -0
- package/dist/index.js +1094 -1135
- package/dist/nuxt.js +0 -1
- package/package.json +38 -36
- package/src/attribute-contract.test.ts +151 -0
- package/src/components/atoms/AnimatedNumber/AnimatedNumber.stories.ts +36 -0
- package/src/components/atoms/AnimatedNumber/AnimatedNumber.test.ts +21 -0
- package/src/components/atoms/Avatar/Avatar.stories.ts +122 -0
- package/src/components/atoms/Avatar/Avatar.test.ts +134 -0
- package/src/components/atoms/Button/Button.stories.ts +290 -0
- package/src/components/atoms/Button/Button.test.ts +406 -0
- package/src/components/atoms/Card/Card.stories.ts +45 -0
- package/src/components/atoms/Card/Card.test.ts +46 -0
- package/src/components/atoms/Checkbox/Checkbox.stories.ts +35 -0
- package/src/components/atoms/Checkbox/Checkbox.test.ts +55 -0
- package/src/components/atoms/Chip/Chip.stories.ts +274 -0
- package/src/components/atoms/Chip/Chip.test.ts +230 -0
- package/src/components/atoms/EnergyLabel/EnergyLabel.stories.ts +189 -0
- package/src/components/atoms/EnergyLabel/EnergyLabel.test.ts +100 -0
- package/src/components/atoms/FloatingPanel/FloatingPanel.stories.ts +677 -0
- package/src/components/atoms/FloatingPanel/FloatingPanel.test.ts +864 -0
- package/src/components/atoms/Icon/Icon.stories.ts +63 -0
- package/src/components/atoms/Icon/Icon.test.ts +89 -0
- package/src/components/atoms/Loader/Loader.stories.ts +36 -0
- package/src/components/atoms/Loader/Loader.test.ts +40 -0
- package/src/components/atoms/PickerItem/PickerItem.stories.ts +172 -0
- package/src/components/atoms/PickerItem/PickerItem.test.ts +142 -0
- package/src/components/atoms/PickerPreview/PickerPreview.stories.ts +183 -0
- package/src/components/atoms/PickerPreview/PickerPreview.test.ts +137 -0
- package/src/components/atoms/PresenceIndicator/PresenceIndicator.stories.ts +21 -0
- package/src/components/atoms/PropertyListing/PropertyListing.stories.ts +38 -0
- package/src/components/atoms/PropertyListing/PropertyListing.test.ts +46 -0
- package/src/components/atoms/SelectableOption/SelectableOption.stories.ts +131 -0
- package/src/components/atoms/SelectableOption/SelectableOption.test.ts +96 -0
- package/src/components/atoms/Skeleton/Skeleton.stories.ts +27 -0
- package/src/components/atoms/Skeleton/Skeleton.test.ts +28 -0
- package/src/components/atoms/Slider/Slider.stories.ts +151 -0
- package/src/components/atoms/Slider/Slider.test.ts +191 -0
- package/src/components/atoms/Squircle/Squircle.stories.ts +114 -0
- package/src/components/atoms/Squircle/Squircle.test.ts +70 -0
- package/src/components/atoms/Switch/Switch.stories.ts +91 -0
- package/src/components/atoms/Switch/Switch.test.ts +183 -0
- package/src/components/atoms/Tooltip/Tooltip.stories.ts +158 -0
- package/src/components/atoms/Tooltip/Tooltip.test.ts +134 -0
- package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.stories.ts +124 -0
- package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.test.ts +464 -0
- package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.stories.ts +266 -0
- package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.test.ts +421 -0
- package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.stories.ts +365 -0
- package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.test.ts +484 -0
- package/src/components/editor/composables/useRadioGrid.test.ts +261 -0
- package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.stories.ts +73 -0
- package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.test.ts +189 -0
- package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.stories.ts +309 -0
- package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.test.ts +359 -0
- package/src/components/editor/molecules/EditorAnchor/EditorAnchor.stories.ts +138 -0
- package/src/components/editor/molecules/EditorAnchor/EditorAnchor.test.ts +344 -0
- package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.stories.ts +87 -0
- package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.test.ts +262 -0
- package/src/components/editor/molecules/EditorBlur/EditorBlur.stories.ts +115 -0
- package/src/components/editor/molecules/EditorBlur/EditorBlur.test.ts +243 -0
- package/src/components/editor/molecules/EditorBorder/EditorBorder.stories.ts +187 -0
- package/src/components/editor/molecules/EditorBorder/EditorBorder.test.ts +456 -0
- package/src/components/editor/molecules/EditorBorder/EditorBorder.vue +58 -2
- package/src/components/editor/molecules/EditorColor/EditorColor.stories.ts +563 -0
- package/src/components/editor/molecules/EditorColor/EditorColor.test.ts +689 -0
- package/src/components/editor/molecules/EditorColor/EditorColorGradient.test.ts +793 -0
- package/src/components/editor/molecules/EditorColor/_ColorField.vue +7 -29
- package/src/components/editor/molecules/EditorColor/_ThemeRoleSwatches.vue +57 -0
- package/src/components/editor/molecules/EditorColor/gradientClass.test.ts +303 -0
- package/src/components/editor/molecules/EditorDashPattern/EditorDashPattern.stories.ts +72 -0
- package/src/components/editor/molecules/EditorDashPattern/EditorDashPattern.test.ts +124 -0
- package/src/components/editor/molecules/EditorFlexDirection/EditorFlexDirection.stories.ts +46 -0
- package/src/components/editor/molecules/EditorFlexDirection/EditorFlexDirection.test.ts +97 -0
- package/src/components/editor/molecules/EditorFontSize/EditorFontSize.stories.ts +248 -0
- package/src/components/editor/molecules/EditorFontSize/EditorFontSize.test.ts +288 -0
- package/src/components/editor/molecules/EditorGap/EditorGap.stories.ts +129 -0
- package/src/components/editor/molecules/EditorGap/EditorGap.test.ts +253 -0
- package/src/components/editor/molecules/EditorHeadingType/EditorHeadingType.stories.ts +68 -0
- package/src/components/editor/molecules/EditorHeadingType/EditorHeadingType.test.ts +150 -0
- package/src/components/editor/molecules/EditorJustifyContent/EditorJustifyContent.stories.ts +71 -0
- package/src/components/editor/molecules/EditorJustifyContent/EditorJustifyContent.test.ts +161 -0
- package/src/components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.stories.ts +204 -0
- package/src/components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.test.ts +238 -0
- package/src/components/editor/molecules/EditorLineHeight/EditorLineHeight.stories.ts +257 -0
- package/src/components/editor/molecules/EditorLineHeight/EditorLineHeight.test.ts +286 -0
- package/src/components/editor/molecules/EditorLineStyle/EditorLineStyle.stories.ts +135 -0
- package/src/components/editor/molecules/EditorLineStyle/EditorLineStyle.test.ts +230 -0
- package/src/components/editor/molecules/EditorMaxWidth/EditorMaxWidth.stories.ts +154 -0
- package/src/components/editor/molecules/EditorMaxWidth/EditorMaxWidth.test.ts +112 -0
- package/src/components/editor/molecules/EditorOverrideStatus/EditorOverrideStatus.stories.ts +206 -0
- package/src/components/editor/molecules/EditorOverrideStatus/EditorOverrideStatus.test.ts +332 -0
- package/src/components/editor/molecules/EditorPadding/EditorPadding.stories.ts +374 -0
- package/src/components/editor/molecules/EditorPadding/EditorPadding.test.ts +712 -0
- package/src/components/editor/molecules/EditorRadius/EditorRadius.stories.ts +64 -0
- package/src/components/editor/molecules/EditorRadius/EditorRadius.test.ts +137 -0
- package/src/components/editor/molecules/EditorShadow/EditorShadow.stories.ts +103 -0
- package/src/components/editor/molecules/EditorShadow/EditorShadow.test.ts +168 -0
- package/src/components/editor/molecules/EditorSize/EditorSize.stories.ts +84 -0
- package/src/components/editor/molecules/EditorSize/EditorSize.test.ts +253 -0
- package/src/components/editor/molecules/EditorSizeLimit/EditorSizeLimit.stories.ts +49 -0
- package/src/components/editor/molecules/EditorSizeLimit/EditorSizeLimit.test.ts +43 -0
- package/src/components/editor/molecules/EditorTextAlign/EditorTextAlign.stories.ts +69 -0
- package/src/components/editor/molecules/EditorTextAlign/EditorTextAlign.test.ts +155 -0
- package/src/components/molecules/Announcement/Announcement.stories.ts +224 -0
- package/src/components/molecules/Announcement/Announcement.test.ts +161 -0
- package/src/components/molecules/ContactInfoCard/ContactInfoCard.stories.ts +99 -0
- package/src/components/molecules/ContactInfoCard/ContactInfoCard.test.ts +70 -0
- package/src/components/molecules/CustomerJourneyTile/CustomerJourneyTile.stories.ts +148 -0
- package/src/components/molecules/CustomerJourneyTile/CustomerJourneyTile.test.ts +271 -0
- package/src/components/molecules/DoughnutChart/DoughnutChart.stories.ts +45 -0
- package/src/components/molecules/DoughnutChart/DoughnutChart.test.ts +205 -0
- package/src/components/molecules/InfoBlock/InfoBlock.stories.ts +84 -0
- package/src/components/molecules/InfoBlock/InfoBlock.test.ts +101 -0
- package/src/components/molecules/InputField/InputField.stories.ts +117 -0
- package/src/components/molecules/InputField/InputField.test.ts +138 -0
- package/src/components/molecules/InputSelect/InputSelect.stories.ts +116 -0
- package/src/components/molecules/InputSelect/InputSelect.test.ts +127 -0
- package/src/components/molecules/ModalFooter/ModalFooter.stories.ts +204 -0
- package/src/components/molecules/ModalFooter/ModalFooter.test.ts +276 -0
- package/src/components/molecules/PageCard/PageCard.stories.ts +100 -0
- package/src/components/molecules/PageCard/PageCard.test.ts +91 -0
- package/src/components/molecules/PageContainer/PageContainer.stories.ts +90 -0
- package/src/components/molecules/PageContainer/PageContainer.test.ts +50 -0
- package/src/components/molecules/PageMockup/PageMockup.stories.ts +38 -0
- package/src/components/molecules/PageMockup/PageMockup.test.ts +20 -0
- package/src/components/molecules/PageTypeTile/PageTypeTile.stories.ts +63 -0
- package/src/components/molecules/PageTypeTile/PageTypeTile.test.ts +41 -0
- package/src/components/molecules/Pagination/Pagination.stories.ts +72 -0
- package/src/components/molecules/Pagination/Pagination.test.ts +130 -0
- package/src/components/molecules/PickerGroup/PickerGroup.stories.ts +90 -0
- package/src/components/molecules/PickerGroup/PickerGroup.test.ts +145 -0
- package/src/components/molecules/PickerList/PickerList.stories.ts +436 -0
- package/src/components/molecules/PickerList/PickerList.test.ts +870 -0
- package/src/components/molecules/PlaceholderInput/PlaceholderInput.stories.ts +48 -0
- package/src/components/molecules/PlaceholderInput/PlaceholderInput.test.ts +107 -0
- package/src/components/molecules/PropertyCard/PropertyCard.stories.ts +46 -0
- package/src/components/molecules/PropertyCard/PropertyCard.test.ts +59 -0
- package/src/components/molecules/Select/Select.stories.ts +264 -0
- package/src/components/molecules/Select/Select.test.ts +315 -0
- package/src/components/molecules/SidebarMenuItem/SidebarMenuItem.stories.ts +84 -0
- package/src/components/molecules/SidebarMenuItem/SidebarMenuItem.test.ts +103 -0
- package/src/components/molecules/SkeletonPropertyCard/SkeletonPropertyCard.stories.ts +11 -0
- package/src/components/molecules/SkeletonPropertyCard/SkeletonPropertyCard.test.ts +38 -0
- package/src/components/molecules/SocialIcons/SocialIcons.stories.ts +28 -0
- package/src/components/molecules/SocialIcons/SocialIcons.test.ts +58 -0
- package/src/components/molecules/SocialMediaCustomTemplate/SocialMediaCustomTemplate.stories.ts +11 -0
- package/src/components/molecules/SocialMediaCustomTemplate/SocialMediaCustomTemplate.test.ts +116 -0
- package/src/components/molecules/SocialMediaType/SocialMediaType.stories.ts +37 -0
- package/src/components/molecules/SocialMediaType/SocialMediaType.test.ts +126 -0
- package/src/components/molecules/Source/Source.stories.ts +35 -0
- package/src/components/molecules/Source/Source.test.ts +45 -0
- package/src/components/molecules/TabbedContent/TabbedContent.stories.ts +96 -0
- package/src/components/molecules/TabbedContent/TabbedContent.test.ts +109 -0
- package/src/components/molecules/Tabs/Tabs.stories.ts +230 -0
- package/src/components/molecules/Tabs/Tabs.test.ts +280 -0
- package/src/components/molecules/TemplateCard/TemplateCard.stories.ts +327 -0
- package/src/components/molecules/TemplateCard/TemplateCard.test.ts +339 -0
- package/src/components/molecules/Textarea/Textarea.stories.ts +216 -0
- package/src/components/molecules/Textarea/Textarea.test.ts +191 -0
- package/src/components/molecules/TimelineEvent/TimelineEvent.stories.ts +319 -0
- package/src/components/molecules/TimelineEvent/TimelineEvent.test.ts +224 -0
- package/src/components/molecules/TimelineLanes/TimelineLanes.stories.ts +29 -0
- package/src/components/molecules/TimelineLanes/TimelineLanes.test.ts +30 -0
- package/src/components/molecules/Toast/Toast.stories.ts +110 -0
- package/src/components/molecules/Toast/Toast.test.ts +103 -0
- package/src/components/molecules/ToggleGroup/ToggleGroup.stories.ts +297 -0
- package/src/components/molecules/ToggleGroup/ToggleGroup.test.ts +578 -0
- package/src/components/molecules/Tree/Tree.stories.ts +884 -0
- package/src/components/molecules/Tree/Tree.test.ts +1247 -0
- package/src/components/molecules/Tree/tree-drop.test.ts +223 -0
- package/src/components/organisms/Calendar/Calendar.stories.ts +108 -0
- package/src/components/organisms/Calendar/Calendar.test.ts +279 -0
- package/src/components/organisms/Calendar/_CalendarDayView.test.ts +104 -0
- package/src/components/organisms/Calendar/_CalendarHeader.test.ts +86 -0
- package/src/components/organisms/Calendar/_CalendarMonthView.test.ts +52 -0
- package/src/components/organisms/Calendar/_CalendarTimePicker.test.ts +89 -0
- package/src/components/organisms/Calendar/_CalendarYearView.test.ts +56 -0
- package/src/components/organisms/ColorPicker/ColorPicker.stories.ts +229 -0
- package/src/components/organisms/ColorPicker/ColorPicker.test.ts +638 -0
- package/src/components/organisms/ColorPicker/gradient.test.ts +67 -0
- package/src/components/organisms/ColorPicker/gradientGeometry.test.ts +206 -0
- package/src/components/organisms/Combobox/Combobox.stories.ts +234 -0
- package/src/components/organisms/Combobox/Combobox.test.ts +512 -0
- package/src/components/organisms/Combobox/Combobox.vue +1 -1
- package/src/components/organisms/ComboboxPanel/ComboboxPanel.stories.ts +158 -0
- package/src/components/organisms/ComboboxPanel/ComboboxPanel.test.ts +524 -0
- package/src/components/organisms/ComboboxPanel/ComboboxPanel.vue +1 -1
- package/src/components/organisms/CountrySelect/CountrySelect.stories.ts +139 -0
- package/src/components/organisms/CountrySelect/CountrySelect.test.ts +149 -0
- package/src/components/organisms/DatePicker/DatePicker.stories.ts +163 -0
- package/src/components/organisms/DatePicker/DatePicker.test.ts +214 -0
- package/src/components/organisms/DropdownButton/DropdownButton.stories.ts +123 -0
- package/src/components/organisms/DropdownButton/DropdownButton.test.ts +379 -0
- package/src/components/organisms/FontPicker/FontPicker.stories.ts +476 -0
- package/src/components/organisms/FontPicker/FontPicker.test.ts +873 -0
- package/src/components/organisms/Header/Header.stories.ts +75 -0
- package/src/components/organisms/Header/Header.test.ts +233 -0
- package/src/components/organisms/IconPicker/IconPicker.stories.ts +734 -0
- package/src/components/organisms/IconPicker/IconPicker.test.ts +669 -0
- package/src/components/organisms/IconPicker/_IconPickerGrid.test.ts +184 -0
- package/src/components/organisms/IconPicker/_IconPickerRail.test.ts +102 -0
- package/src/components/organisms/IconPicker/useIconPickerNavigation.test.ts +215 -0
- package/src/components/organisms/Modal/Modal.stories.ts +350 -0
- package/src/components/organisms/Modal/Modal.test.ts +275 -0
- package/src/components/organisms/PickerPanel/PickerPanel.stories.ts +452 -0
- package/src/components/organisms/PickerPanel/PickerPanel.test.ts +358 -0
- package/src/components/organisms/ProgressBar/ProgressBar.stories.ts +100 -0
- package/src/components/organisms/ProgressBar/ProgressBar.test.ts +417 -0
- package/src/components/organisms/ReferenceCard/ReferenceCard.stories.ts +191 -0
- package/src/components/organisms/ReferenceCard/ReferenceCard.test.ts +165 -0
- package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.stories.ts +65 -0
- package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.test.ts +466 -0
- package/src/components/organisms/StepperHeader/StepperHeader.stories.ts +91 -0
- package/src/components/organisms/StepperHeader/StepperHeader.test.ts +279 -0
- package/src/components/organisms/Table/Table.stories.ts +332 -0
- package/src/components/organisms/Table/Table.test.ts +320 -0
- package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.stories.ts +198 -0
- package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.test.ts +283 -0
- package/src/components/organisms/TimelineTrack/TimelineTrack.stories.ts +120 -0
- package/src/components/organisms/TimelineTrack/TimelineTrack.test.ts +91 -0
- package/src/components/organisms/Toaster/Toaster.stories.ts +51 -0
- package/src/components/organisms/Toaster/Toaster.test.ts +75 -0
- package/src/components/organisms/VideoControls/VideoControls.stories.ts +792 -0
- package/src/components/organisms/VideoControls/VideoControls.test.ts +1056 -0
- package/src/components/organisms/VideoPlayer/VideoPlayer.stories.ts +327 -0
- package/src/components/organisms/VideoPlayer/VideoPlayer.test.ts +439 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import type { NexxtTreeNode } from './Tree.vue'
|
|
3
|
+
import {
|
|
4
|
+
ghostSlot,
|
|
5
|
+
hitTest,
|
|
6
|
+
isNoOp,
|
|
7
|
+
resolveDrop,
|
|
8
|
+
samePlace,
|
|
9
|
+
type RestSlot,
|
|
10
|
+
type TreeDrop,
|
|
11
|
+
type TreeRow,
|
|
12
|
+
} from './tree-drop'
|
|
13
|
+
|
|
14
|
+
const NODES: NexxtTreeNode[] = [
|
|
15
|
+
{
|
|
16
|
+
id: 'page',
|
|
17
|
+
label: 'Pagina',
|
|
18
|
+
children: [
|
|
19
|
+
{ id: 'image', label: 'Afbeelding' },
|
|
20
|
+
{
|
|
21
|
+
id: 'group',
|
|
22
|
+
label: 'Groep',
|
|
23
|
+
children: [
|
|
24
|
+
{ id: 'title', label: 'Titel' },
|
|
25
|
+
{ id: 'text', label: 'Tekst' },
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
{ id: 'empty', label: 'Lege groep', children: [] },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
{ id: 'about', label: 'Over ons' },
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
/** The visible rows of `NODES`, as the tree flattens them. */
|
|
35
|
+
const rowsOf = (collapsed: string[] = []) => {
|
|
36
|
+
const rows: TreeRow[] = []
|
|
37
|
+
const walk = (nodes: NexxtTreeNode[], depth: number, parentId: string | null) =>
|
|
38
|
+
nodes.forEach((node, index) => {
|
|
39
|
+
const expandable = !!node.children?.length
|
|
40
|
+
const expanded = expandable && !collapsed.includes(node.id)
|
|
41
|
+
rows.push({
|
|
42
|
+
node,
|
|
43
|
+
depth,
|
|
44
|
+
parentId,
|
|
45
|
+
expandable,
|
|
46
|
+
canHaveChildren: !!node.children,
|
|
47
|
+
draggable: true,
|
|
48
|
+
expanded,
|
|
49
|
+
position: index + 1,
|
|
50
|
+
setSize: nodes.length,
|
|
51
|
+
})
|
|
52
|
+
if (expanded) walk(node.children!, depth + 1, node.id)
|
|
53
|
+
})
|
|
54
|
+
walk(NODES, 0, null)
|
|
55
|
+
|
|
56
|
+
return rows
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const row = (id: string, collapsed?: string[]) =>
|
|
60
|
+
rowsOf(collapsed).find((item) => item.node.id === id)!
|
|
61
|
+
|
|
62
|
+
const drop = (parentId: string | null, index: number): TreeDrop => ({
|
|
63
|
+
rowId: 'any',
|
|
64
|
+
parentId,
|
|
65
|
+
index,
|
|
66
|
+
marker: 'before',
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('hitTest', () => {
|
|
70
|
+
// three 28px rows with a 2px gap under each
|
|
71
|
+
const slots: RestSlot[] = ['a', 'b', 'c'].map((id, index) => ({
|
|
72
|
+
id,
|
|
73
|
+
top: index * 30,
|
|
74
|
+
height: 28,
|
|
75
|
+
bottom: index * 30 + 30,
|
|
76
|
+
}))
|
|
77
|
+
|
|
78
|
+
it('reads the row under the pointer and how far down it is', () => {
|
|
79
|
+
expect(hitTest({ slots, ghost: null }, 37)).toEqual({ kind: 'row', id: 'b', ratio: 0.25 })
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('counts a gap as the bottom of the row above it', () => {
|
|
83
|
+
expect(hitTest({ slots, ghost: null }, 29)).toEqual({ kind: 'row', id: 'a', ratio: 1 })
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('keeps the spot while the pointer is on the ghost row, its gap included', () => {
|
|
87
|
+
const ghost = { top: 30, bottom: 60 }
|
|
88
|
+
|
|
89
|
+
expect(hitTest({ slots, ghost }, 30)).toEqual({ kind: 'keep' })
|
|
90
|
+
expect(hitTest({ slots, ghost }, 59)).toEqual({ kind: 'keep' })
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('reads a pointer below the ghost row against where the rows are without it', () => {
|
|
94
|
+
const ghost = { top: 30, bottom: 60 }
|
|
95
|
+
|
|
96
|
+
// `b` is drawn at 60 now, but it is `b` at rest that counts
|
|
97
|
+
expect(hitTest({ slots, ghost }, 67)).toEqual({ kind: 'row', id: 'b', ratio: 0.25 })
|
|
98
|
+
expect(hitTest({ slots, ghost }, 67)).toEqual(hitTest({ slots, ghost: null }, 37))
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('reads the empty space below the last row as the bottom of that row', () => {
|
|
102
|
+
expect(hitTest({ slots, ghost: null }, 200)).toEqual({ kind: 'row', id: 'c', ratio: 1 })
|
|
103
|
+
// with the ghost row at the end, below it is still the same spot
|
|
104
|
+
expect(hitTest({ slots, ghost: { top: 90, bottom: 120 } }, 230)).toEqual({
|
|
105
|
+
kind: 'row',
|
|
106
|
+
id: 'c',
|
|
107
|
+
ratio: 1,
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('keeps the spot when there are no rows', () => {
|
|
112
|
+
expect(hitTest({ slots: [], ghost: null }, 10)).toEqual({ kind: 'keep' })
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
describe('resolveDrop', () => {
|
|
117
|
+
const image = row('image').node
|
|
118
|
+
|
|
119
|
+
it('splits a leaf at its middle', () => {
|
|
120
|
+
expect(resolveDrop(row('title'), 0.49, image)).toMatchObject({
|
|
121
|
+
parentId: 'group',
|
|
122
|
+
index: 0,
|
|
123
|
+
marker: 'before',
|
|
124
|
+
})
|
|
125
|
+
expect(resolveDrop(row('title'), 0.5, image)).toMatchObject({
|
|
126
|
+
parentId: 'group',
|
|
127
|
+
index: 1,
|
|
128
|
+
marker: 'after',
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('splits a group into before, inside and after', () => {
|
|
133
|
+
expect(resolveDrop(row('group'), 0.3, image)).toMatchObject({ marker: 'before', index: 1 })
|
|
134
|
+
expect(resolveDrop(row('group'), 0.69, image)).toMatchObject({ marker: 'inside' })
|
|
135
|
+
expect(resolveDrop(row('group'), 0.7, image)).toMatchObject({ marker: 'after', index: 2 })
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('lands inside always as the first child, wherever in the middle the pointer is', () => {
|
|
139
|
+
for (const ratio of [0.31, 0.5, 0.69]) {
|
|
140
|
+
expect(resolveDrop(row('group'), ratio, image)).toEqual({
|
|
141
|
+
rowId: 'group',
|
|
142
|
+
parentId: 'group',
|
|
143
|
+
index: 0,
|
|
144
|
+
marker: 'inside',
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('accepts an empty group as a parent', () => {
|
|
150
|
+
expect(resolveDrop(row('empty'), 0.5, image)).toMatchObject({ parentId: 'empty', index: 0 })
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('refuses the dragged node and everything inside it', () => {
|
|
154
|
+
const group = row('group').node
|
|
155
|
+
|
|
156
|
+
expect(resolveDrop(row('group'), 0.1, group)).toBeNull()
|
|
157
|
+
expect(resolveDrop(row('group'), 0.5, group)).toBeNull()
|
|
158
|
+
expect(resolveDrop(row('title'), 0.9, group)).toBeNull()
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
describe('isNoOp', () => {
|
|
163
|
+
it('is the spot right before or right after the node itself', () => {
|
|
164
|
+
const title = row('title')
|
|
165
|
+
|
|
166
|
+
expect(isNoOp(drop('group', 0), title)).toBe(true)
|
|
167
|
+
expect(isNoOp(drop('group', 1), title)).toBe(true)
|
|
168
|
+
expect(isNoOp(drop('group', 2), title)).toBe(false)
|
|
169
|
+
expect(isNoOp(drop('page', 0), title)).toBe(false)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('includes inside on its own parent, now that inside lands first', () => {
|
|
173
|
+
const inside = resolveDrop(row('group'), 0.5, row('image').node)!
|
|
174
|
+
|
|
175
|
+
expect(isNoOp(inside, row('title'))).toBe(true)
|
|
176
|
+
expect(isNoOp(inside, row('text'))).toBe(false)
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
describe('samePlace', () => {
|
|
181
|
+
it('matches spots by where they land, not by the row that picked them', () => {
|
|
182
|
+
const afterTitle = { ...drop('group', 1), rowId: 'title', marker: 'after' as const }
|
|
183
|
+
const beforeText = { ...drop('group', 1), rowId: 'text' }
|
|
184
|
+
|
|
185
|
+
expect(samePlace(afterTitle, beforeText)).toBe(true)
|
|
186
|
+
expect(samePlace(afterTitle, drop('group', 2))).toBe(false)
|
|
187
|
+
expect(samePlace(null, null)).toBe(true)
|
|
188
|
+
expect(samePlace(afterTitle, null)).toBe(false)
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
describe('ghostSlot', () => {
|
|
193
|
+
const ids = (rows: TreeRow[]) => rows.map((item) => item.node.id)
|
|
194
|
+
|
|
195
|
+
it('goes right before the sibling that is at the index now', () => {
|
|
196
|
+
const rows = rowsOf()
|
|
197
|
+
|
|
198
|
+
expect(ids(rows)).toEqual(['page', 'image', 'group', 'title', 'text', 'empty', 'about'])
|
|
199
|
+
expect(ghostSlot(rows, NODES, drop('group', 1))).toEqual({ at: 4, depth: 2 })
|
|
200
|
+
expect(ghostSlot(rows, NODES, drop('page', 0))).toEqual({ at: 1, depth: 1 })
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('goes after everything shown inside the parent at the end of its children', () => {
|
|
204
|
+
const rows = rowsOf()
|
|
205
|
+
|
|
206
|
+
expect(ghostSlot(rows, NODES, drop('group', 2))).toEqual({ at: 5, depth: 2 })
|
|
207
|
+
// past `group` and its children, after `empty`
|
|
208
|
+
expect(ghostSlot(rows, NODES, drop('page', 3))).toEqual({ at: 6, depth: 1 })
|
|
209
|
+
expect(ghostSlot(rows, NODES, drop(null, 2))).toEqual({ at: 7, depth: 0 })
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('goes right under an empty group', () => {
|
|
213
|
+
expect(ghostSlot(rowsOf(), NODES, drop('empty', 0))).toEqual({ at: 6, depth: 2 })
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('leaves a collapsed group to mark itself', () => {
|
|
217
|
+
expect(ghostSlot(rowsOf(['group']), NODES, drop('group', 0))).toBeNull()
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it('has no spot without a drop', () => {
|
|
221
|
+
expect(ghostSlot(rowsOf(), NODES, null)).toBeNull()
|
|
222
|
+
})
|
|
223
|
+
})
|
|
@@ -0,0 +1,108 @@
|
|
|
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/Organisms/Calendar',
|
|
7
|
+
component: Calendar,
|
|
8
|
+
} satisfies Meta<typeof Calendar>
|
|
9
|
+
|
|
10
|
+
type Story = StoryObj<typeof Calendar>
|
|
11
|
+
|
|
12
|
+
export const Default: Story = {
|
|
13
|
+
args: {},
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const WithSelectedDate: Story = {
|
|
17
|
+
args: {
|
|
18
|
+
modelValue: new Date(),
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const WithMarkedDates: Story = {
|
|
23
|
+
args: {
|
|
24
|
+
markedDates: [
|
|
25
|
+
new Date(new Date().getFullYear(), new Date().getMonth(), 5),
|
|
26
|
+
new Date(new Date().getFullYear(), new Date().getMonth(), 12),
|
|
27
|
+
new Date(new Date().getFullYear(), new Date().getMonth(), 20),
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const WithDisabledDates: Story = {
|
|
33
|
+
args: {
|
|
34
|
+
disabledDates: [
|
|
35
|
+
new Date(new Date().getFullYear(), new Date().getMonth(), 10),
|
|
36
|
+
new Date(new Date().getFullYear(), new Date().getMonth(), 11),
|
|
37
|
+
new Date(new Date().getFullYear(), new Date().getMonth(), 12),
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const WithDateRange: Story = {
|
|
43
|
+
args: {
|
|
44
|
+
minDate: new Date(new Date().getFullYear(), new Date().getMonth(), 5),
|
|
45
|
+
maxDate: new Date(new Date().getFullYear(), new Date().getMonth() + 1, 20),
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const WithMonthYearPicker: Story = {
|
|
50
|
+
args: {
|
|
51
|
+
monthYearPicker: true,
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const EnglishLocale: Story = {
|
|
56
|
+
args: {
|
|
57
|
+
locale: enUS,
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const DateTime: Story = {
|
|
62
|
+
render: (args) => ({
|
|
63
|
+
components: { Calendar },
|
|
64
|
+
setup() {
|
|
65
|
+
const selected = ref<Date | null>(new Date())
|
|
66
|
+
return { args, selected }
|
|
67
|
+
},
|
|
68
|
+
template: `
|
|
69
|
+
<Calendar
|
|
70
|
+
v-bind="args"
|
|
71
|
+
v-model="selected"
|
|
72
|
+
datetime
|
|
73
|
+
/>
|
|
74
|
+
<p v-if="selected" style="margin-top: 12px; font-size: 13px; color: #555; display: block;">
|
|
75
|
+
Geselecteerd: {{ selected.toLocaleString('nl-NL', { dateStyle: 'short', timeStyle: 'short' }) }}
|
|
76
|
+
</p>
|
|
77
|
+
`,
|
|
78
|
+
}),
|
|
79
|
+
args: {
|
|
80
|
+
datetime: true,
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const Playground: Story = {
|
|
85
|
+
render: (args) => ({
|
|
86
|
+
components: { Calendar },
|
|
87
|
+
setup() {
|
|
88
|
+
const selected = ref<Date | null>(null)
|
|
89
|
+
return { args, selected }
|
|
90
|
+
},
|
|
91
|
+
template: `
|
|
92
|
+
<Calendar
|
|
93
|
+
v-bind="args"
|
|
94
|
+
v-model="selected"
|
|
95
|
+
/>
|
|
96
|
+
<p v-if="selected" style="margin-top: 12px; font-size: 13px; color: #555; display: block;">
|
|
97
|
+
Geselecteerd: {{ args.datetime ? selected.toLocaleString('nl-NL', { dateStyle: 'short', timeStyle: 'short' }) : selected.toLocaleDateString('nl-NL') }}
|
|
98
|
+
</p>
|
|
99
|
+
`,
|
|
100
|
+
}),
|
|
101
|
+
args: {
|
|
102
|
+
monthYearPicker: true,
|
|
103
|
+
markedDates: [
|
|
104
|
+
new Date(new Date().getFullYear(), new Date().getMonth(), 8),
|
|
105
|
+
new Date(new Date().getFullYear(), new Date().getMonth(), 15),
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
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
|
+
})
|