@nexxtmove/ui 2.6.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/index.js +49 -126
- 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/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/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,315 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { mount, flushPromises } from '@vue/test-utils'
|
|
3
|
+
import { ref, nextTick } from 'vue'
|
|
4
|
+
import NexxtSelect from './Select.vue'
|
|
5
|
+
|
|
6
|
+
vi.mock('@floating-ui/vue', async (importOriginal) => {
|
|
7
|
+
const actual = await importOriginal<typeof import('@floating-ui/vue')>()
|
|
8
|
+
return {
|
|
9
|
+
...actual,
|
|
10
|
+
useFloating: () => ({
|
|
11
|
+
floatingStyles: ref({}),
|
|
12
|
+
placement: ref('bottom-start'),
|
|
13
|
+
isPositioned: ref(true),
|
|
14
|
+
}),
|
|
15
|
+
autoUpdate: () => () => {},
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const stringOptions = ['Optie 1', 'Optie 2', 'Optie 3']
|
|
20
|
+
const objectOptions = [
|
|
21
|
+
{ label: 'Amsterdam', value: 'ams' },
|
|
22
|
+
{ label: 'Rotterdam', value: 'rot' },
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
const open = async (wrapper: ReturnType<typeof mount>) => {
|
|
26
|
+
await wrapper.find('[role="combobox"]').trigger('click')
|
|
27
|
+
await flushPromises()
|
|
28
|
+
await nextTick()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('NexxtSelect', () => {
|
|
32
|
+
it('renders label and placeholder', () => {
|
|
33
|
+
const wrapper = mount(NexxtSelect, {
|
|
34
|
+
props: { options: stringOptions, label: 'Keuze', placeholder: 'Kies een optie' },
|
|
35
|
+
})
|
|
36
|
+
expect(wrapper.text()).toContain('Keuze')
|
|
37
|
+
expect(wrapper.text()).toContain('Kies een optie')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('opens dropdown and shows options on click', async () => {
|
|
41
|
+
const wrapper = mount(NexxtSelect, { props: { options: stringOptions } })
|
|
42
|
+
await open(wrapper)
|
|
43
|
+
const items = wrapper.findAll('[role="option"]')
|
|
44
|
+
expect(items).toHaveLength(stringOptions.length)
|
|
45
|
+
expect(items[0].text()).toBe('Optie 1')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('emits update:modelValue when an option is selected', async () => {
|
|
49
|
+
const wrapper = mount(NexxtSelect, { props: { options: stringOptions, modelValue: null } })
|
|
50
|
+
await open(wrapper)
|
|
51
|
+
await wrapper.find('[role="option"]').trigger('click')
|
|
52
|
+
expect(wrapper.emitted()['update:modelValue'][0]).toEqual(['Optie 1'])
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('works with object options', async () => {
|
|
56
|
+
const wrapper = mount(NexxtSelect, { props: { options: objectOptions, modelValue: null } })
|
|
57
|
+
await open(wrapper)
|
|
58
|
+
const items = wrapper.findAll('[role="option"]')
|
|
59
|
+
expect(items[0].text()).toBe('Amsterdam')
|
|
60
|
+
await items[1].trigger('click')
|
|
61
|
+
expect(wrapper.emitted()['update:modelValue'][0]).toEqual(['rot'])
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('displays the selected label when a value is set', () => {
|
|
65
|
+
const wrapper = mount(NexxtSelect, { props: { options: objectOptions, modelValue: 'ams' } })
|
|
66
|
+
expect(wrapper.text()).toContain('Amsterdam')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// The width itself is only measurable in a browser (see the Narrow story);
|
|
70
|
+
// jsdom can check the structure the ellipsis depends on.
|
|
71
|
+
it('renders the field text in an element of its own, so it can be truncated', () => {
|
|
72
|
+
const wrapper = mount(NexxtSelect, { props: { options: objectOptions, modelValue: 'ams' } })
|
|
73
|
+
const text = wrapper.find('[role="combobox"] .truncate')
|
|
74
|
+
expect(text.text()).toBe('Amsterdam')
|
|
75
|
+
expect(text.element.children).toHaveLength(0)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('shows error ring and error message', () => {
|
|
79
|
+
const wrapper = mount(NexxtSelect, {
|
|
80
|
+
props: { options: stringOptions, error: true, errorMessage: 'Verplicht veld' },
|
|
81
|
+
})
|
|
82
|
+
expect(wrapper.find('.ring-brick-400').exists()).toBe(true)
|
|
83
|
+
expect(wrapper.text()).toContain('Verplicht veld')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('renders required asterisk when required is true', () => {
|
|
87
|
+
const wrapper = mount(NexxtSelect, {
|
|
88
|
+
props: { options: stringOptions, label: 'Keuze', required: true },
|
|
89
|
+
})
|
|
90
|
+
const asterisk = wrapper.find('.text-brick-500')
|
|
91
|
+
expect(asterisk.exists()).toBe(true)
|
|
92
|
+
expect(asterisk.text()).toBe('*')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('shows the selected option icon in the field', () => {
|
|
96
|
+
const wrapper = mount(NexxtSelect, {
|
|
97
|
+
props: {
|
|
98
|
+
options: [
|
|
99
|
+
{ label: 'Heading 1', value: 'h1', icon: 'h1' as const },
|
|
100
|
+
{ label: 'Heading 2', value: 'h2', icon: 'h2' as const },
|
|
101
|
+
],
|
|
102
|
+
modelValue: 'h2',
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
expect(wrapper.find('[role="combobox"] i').classes()).toContain('fa-h2')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('lets an explicit leftIcon win over the selected option icon', () => {
|
|
110
|
+
const wrapper = mount(NexxtSelect, {
|
|
111
|
+
props: {
|
|
112
|
+
options: [{ label: 'Heading 1', value: 'h1', icon: 'h1' as const }],
|
|
113
|
+
modelValue: 'h1',
|
|
114
|
+
leftIcon: 'location-dot',
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
const icons = wrapper.find('[role="combobox"]').findAll('i')
|
|
119
|
+
expect(icons.some((icon) => icon.classes().includes('fa-location-dot'))).toBe(true)
|
|
120
|
+
expect(icons.some((icon) => icon.classes().includes('fa-h1'))).toBe(false)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('marks the selected option with aria-selected', async () => {
|
|
124
|
+
const wrapper = mount(NexxtSelect, { props: { options: objectOptions, modelValue: 'rot' } })
|
|
125
|
+
await open(wrapper)
|
|
126
|
+
const selected = wrapper.find('[aria-selected="true"]')
|
|
127
|
+
expect(selected.text()).toBe('Rotterdam')
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
const combobox = (wrapper: ReturnType<typeof mount>) => wrapper.find('[role="combobox"]')
|
|
132
|
+
|
|
133
|
+
const press = async (wrapper: ReturnType<typeof mount>, key: string) => {
|
|
134
|
+
await combobox(wrapper).trigger('keydown', { key })
|
|
135
|
+
await flushPromises()
|
|
136
|
+
await nextTick()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Resolves aria-activedescendant to the option element it points at, so assertions
|
|
140
|
+
// can be made about *which option* is highlighted rather than about an attribute.
|
|
141
|
+
const highlighted = (wrapper: ReturnType<typeof mount>) => {
|
|
142
|
+
const id = combobox(wrapper).attributes('aria-activedescendant')
|
|
143
|
+
if (!id) return null
|
|
144
|
+
return wrapper.findAll('[role="option"]').find((o) => o.attributes('id') === id) ?? null
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const isOpen = (wrapper: ReturnType<typeof mount>) => wrapper.find('[role="listbox"]').exists()
|
|
148
|
+
|
|
149
|
+
describe('NexxtSelect keyboard navigation', () => {
|
|
150
|
+
it('has no active descendant while closed', () => {
|
|
151
|
+
const wrapper = mount(NexxtSelect, { props: { options: stringOptions } })
|
|
152
|
+
|
|
153
|
+
expect(combobox(wrapper).attributes('aria-activedescendant')).toBeUndefined()
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('opens on ArrowDown and highlights the first option', async () => {
|
|
157
|
+
const wrapper = mount(NexxtSelect, { props: { options: stringOptions } })
|
|
158
|
+
await press(wrapper, 'ArrowDown')
|
|
159
|
+
|
|
160
|
+
expect(isOpen(wrapper)).toBe(true)
|
|
161
|
+
expect(highlighted(wrapper)?.text()).toBe('Optie 1')
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('opens on ArrowUp and highlights the first option when nothing is selected', async () => {
|
|
165
|
+
const wrapper = mount(NexxtSelect, { props: { options: stringOptions } })
|
|
166
|
+
await press(wrapper, 'ArrowUp')
|
|
167
|
+
|
|
168
|
+
expect(isOpen(wrapper)).toBe(true)
|
|
169
|
+
expect(highlighted(wrapper)?.text()).toBe('Optie 1')
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('opens with the already selected option highlighted', async () => {
|
|
173
|
+
const wrapper = mount(NexxtSelect, {
|
|
174
|
+
props: { options: objectOptions, modelValue: 'rot' },
|
|
175
|
+
})
|
|
176
|
+
await press(wrapper, 'ArrowDown')
|
|
177
|
+
|
|
178
|
+
expect(highlighted(wrapper)?.text()).toBe('Rotterdam')
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('moves down through the options with ArrowDown', async () => {
|
|
182
|
+
const wrapper = mount(NexxtSelect, { props: { options: stringOptions } })
|
|
183
|
+
await press(wrapper, 'ArrowDown')
|
|
184
|
+
await press(wrapper, 'ArrowDown')
|
|
185
|
+
expect(highlighted(wrapper)?.text()).toBe('Optie 2')
|
|
186
|
+
|
|
187
|
+
await press(wrapper, 'ArrowDown')
|
|
188
|
+
expect(highlighted(wrapper)?.text()).toBe('Optie 3')
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('wraps from the last option back to the first on ArrowDown', async () => {
|
|
192
|
+
const wrapper = mount(NexxtSelect, { props: { options: stringOptions } })
|
|
193
|
+
await press(wrapper, 'ArrowDown')
|
|
194
|
+
await press(wrapper, 'ArrowDown')
|
|
195
|
+
await press(wrapper, 'ArrowDown')
|
|
196
|
+
await press(wrapper, 'ArrowDown')
|
|
197
|
+
|
|
198
|
+
expect(highlighted(wrapper)?.text()).toBe('Optie 1')
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('wraps from the first option to the last on ArrowUp', async () => {
|
|
202
|
+
const wrapper = mount(NexxtSelect, { props: { options: stringOptions } })
|
|
203
|
+
await press(wrapper, 'ArrowDown')
|
|
204
|
+
await press(wrapper, 'ArrowUp')
|
|
205
|
+
|
|
206
|
+
expect(highlighted(wrapper)?.text()).toBe('Optie 3')
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('moves up through the options with ArrowUp', async () => {
|
|
210
|
+
const wrapper = mount(NexxtSelect, { props: { options: stringOptions } })
|
|
211
|
+
await press(wrapper, 'ArrowDown')
|
|
212
|
+
await press(wrapper, 'ArrowDown')
|
|
213
|
+
await press(wrapper, 'ArrowUp')
|
|
214
|
+
|
|
215
|
+
expect(highlighted(wrapper)?.text()).toBe('Optie 1')
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('jumps to the first option on Home and the last on End', async () => {
|
|
219
|
+
const wrapper = mount(NexxtSelect, { props: { options: stringOptions } })
|
|
220
|
+
await press(wrapper, 'ArrowDown')
|
|
221
|
+
await press(wrapper, 'End')
|
|
222
|
+
expect(highlighted(wrapper)?.text()).toBe('Optie 3')
|
|
223
|
+
|
|
224
|
+
await press(wrapper, 'Home')
|
|
225
|
+
expect(highlighted(wrapper)?.text()).toBe('Optie 1')
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('selects the highlighted option on Enter and closes', async () => {
|
|
229
|
+
const wrapper = mount(NexxtSelect, {
|
|
230
|
+
props: { options: objectOptions, modelValue: null },
|
|
231
|
+
})
|
|
232
|
+
await press(wrapper, 'ArrowDown')
|
|
233
|
+
await press(wrapper, 'ArrowDown')
|
|
234
|
+
expect(highlighted(wrapper)?.text()).toBe('Rotterdam')
|
|
235
|
+
|
|
236
|
+
await press(wrapper, 'Enter')
|
|
237
|
+
|
|
238
|
+
expect(wrapper.emitted()['update:modelValue'][0]).toEqual(['rot'])
|
|
239
|
+
expect(isOpen(wrapper)).toBe(false)
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
it('selects the first option on Enter straight after opening with ArrowDown', async () => {
|
|
243
|
+
const wrapper = mount(NexxtSelect, {
|
|
244
|
+
props: { options: objectOptions, modelValue: null },
|
|
245
|
+
})
|
|
246
|
+
await press(wrapper, 'ArrowDown')
|
|
247
|
+
await press(wrapper, 'Enter')
|
|
248
|
+
|
|
249
|
+
expect(wrapper.emitted()['update:modelValue'][0]).toEqual(['ams'])
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('still opens on Enter from a closed state without selecting anything', async () => {
|
|
253
|
+
const wrapper = mount(NexxtSelect, {
|
|
254
|
+
props: { options: stringOptions, modelValue: null },
|
|
255
|
+
})
|
|
256
|
+
await press(wrapper, 'Enter')
|
|
257
|
+
|
|
258
|
+
expect(isOpen(wrapper)).toBe(true)
|
|
259
|
+
expect(wrapper.emitted()['update:modelValue']).toBeUndefined()
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it('closes on Escape without selecting', async () => {
|
|
263
|
+
const wrapper = mount(NexxtSelect, {
|
|
264
|
+
props: { options: stringOptions, modelValue: null },
|
|
265
|
+
})
|
|
266
|
+
await press(wrapper, 'ArrowDown')
|
|
267
|
+
await press(wrapper, 'ArrowDown')
|
|
268
|
+
await press(wrapper, 'Escape')
|
|
269
|
+
|
|
270
|
+
expect(isOpen(wrapper)).toBe(false)
|
|
271
|
+
expect(wrapper.emitted()['update:modelValue']).toBeUndefined()
|
|
272
|
+
expect(combobox(wrapper).attributes('aria-activedescendant')).toBeUndefined()
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
it('still toggles open and shut on Space without selecting', async () => {
|
|
276
|
+
const wrapper = mount(NexxtSelect, {
|
|
277
|
+
props: { options: stringOptions, modelValue: null },
|
|
278
|
+
})
|
|
279
|
+
await press(wrapper, ' ')
|
|
280
|
+
expect(isOpen(wrapper)).toBe(true)
|
|
281
|
+
|
|
282
|
+
await press(wrapper, ' ')
|
|
283
|
+
expect(isOpen(wrapper)).toBe(false)
|
|
284
|
+
expect(wrapper.emitted()['update:modelValue']).toBeUndefined()
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it('points aria-controls at the listbox it opens', async () => {
|
|
288
|
+
const wrapper = mount(NexxtSelect, { props: { options: stringOptions } })
|
|
289
|
+
await press(wrapper, 'ArrowDown')
|
|
290
|
+
|
|
291
|
+
const controls = combobox(wrapper).attributes('aria-controls')
|
|
292
|
+
expect(controls).toBeTruthy()
|
|
293
|
+
expect(wrapper.find('[role="listbox"]').attributes('id')).toBe(controls)
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
it('gives every option a unique id for the highlight to reference', async () => {
|
|
297
|
+
const wrapper = mount(NexxtSelect, { props: { options: stringOptions } })
|
|
298
|
+
await press(wrapper, 'ArrowDown')
|
|
299
|
+
|
|
300
|
+
const ids = wrapper.findAll('[role="option"]').map((o) => o.attributes('id'))
|
|
301
|
+
expect(ids.every(Boolean)).toBe(true)
|
|
302
|
+
expect(new Set(ids).size).toBe(ids.length)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('keeps aria-selected on the model value, independent of the highlight', async () => {
|
|
306
|
+
const wrapper = mount(NexxtSelect, {
|
|
307
|
+
props: { options: objectOptions, modelValue: 'ams' },
|
|
308
|
+
})
|
|
309
|
+
await press(wrapper, 'ArrowDown')
|
|
310
|
+
await press(wrapper, 'ArrowDown')
|
|
311
|
+
|
|
312
|
+
expect(highlighted(wrapper)?.text()).toBe('Rotterdam')
|
|
313
|
+
expect(wrapper.find('[aria-selected="true"]').text()).toBe('Amsterdam')
|
|
314
|
+
})
|
|
315
|
+
})
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
2
|
+
import SidebarMenuItem from './SidebarMenuItem.vue'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Components/Molecules/Sidebar Menu Item',
|
|
6
|
+
component: SidebarMenuItem,
|
|
7
|
+
decorators: [
|
|
8
|
+
() => ({
|
|
9
|
+
template:
|
|
10
|
+
'<div class="group" style="background-color: oklch(58.312% 0.19293 256.79281); width: 240px;"><story /></div>',
|
|
11
|
+
}),
|
|
12
|
+
],
|
|
13
|
+
} satisfies Meta<typeof SidebarMenuItem>
|
|
14
|
+
|
|
15
|
+
type Story = StoryObj<typeof SidebarMenuItem>
|
|
16
|
+
|
|
17
|
+
export const Default: Story = {
|
|
18
|
+
args: {
|
|
19
|
+
icon: 'house',
|
|
20
|
+
},
|
|
21
|
+
render: (args) => ({
|
|
22
|
+
components: { SidebarMenuItem },
|
|
23
|
+
setup: () => ({ args }),
|
|
24
|
+
template: '<SidebarMenuItem v-bind="args">Dashboard</SidebarMenuItem>',
|
|
25
|
+
}),
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const WithNotification: Story = {
|
|
29
|
+
args: {
|
|
30
|
+
icon: 'bell',
|
|
31
|
+
notificationCount: 5,
|
|
32
|
+
},
|
|
33
|
+
render: (args) => ({
|
|
34
|
+
components: { SidebarMenuItem },
|
|
35
|
+
setup: () => ({ args }),
|
|
36
|
+
template: '<SidebarMenuItem v-bind="args">Notifications</SidebarMenuItem>',
|
|
37
|
+
}),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const CurrentRoute: Story = {
|
|
41
|
+
args: {
|
|
42
|
+
icon: 'house',
|
|
43
|
+
isCurentRoute: true,
|
|
44
|
+
},
|
|
45
|
+
render: (args) => ({
|
|
46
|
+
components: { SidebarMenuItem },
|
|
47
|
+
setup: () => ({ args }),
|
|
48
|
+
template: '<SidebarMenuItem v-bind="args">Dashboard</SidebarMenuItem>',
|
|
49
|
+
}),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const WhatsApp: Story = {
|
|
53
|
+
args: {
|
|
54
|
+
icon: 'whatsapp',
|
|
55
|
+
},
|
|
56
|
+
render: (args) => ({
|
|
57
|
+
components: { SidebarMenuItem },
|
|
58
|
+
setup: () => ({ args }),
|
|
59
|
+
template: '<SidebarMenuItem v-bind="args">WhatsApp</SidebarMenuItem>',
|
|
60
|
+
}),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const Hubspot: Story = {
|
|
64
|
+
args: {
|
|
65
|
+
icon: 'hubspot',
|
|
66
|
+
},
|
|
67
|
+
render: (args) => ({
|
|
68
|
+
components: { SidebarMenuItem },
|
|
69
|
+
setup: () => ({ args }),
|
|
70
|
+
template: '<SidebarMenuItem v-bind="args">HubSpot</SidebarMenuItem>',
|
|
71
|
+
}),
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const Danger: Story = {
|
|
75
|
+
args: {
|
|
76
|
+
icon: 'power-off',
|
|
77
|
+
variant: 'danger',
|
|
78
|
+
},
|
|
79
|
+
render: (args) => ({
|
|
80
|
+
components: { SidebarMenuItem },
|
|
81
|
+
setup: () => ({ args }),
|
|
82
|
+
template: '<SidebarMenuItem v-bind="args">Uitloggen</SidebarMenuItem>',
|
|
83
|
+
}),
|
|
84
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import SidebarMenuItem from './SidebarMenuItem.vue'
|
|
4
|
+
|
|
5
|
+
const stubs = {
|
|
6
|
+
NexxtIcon: {
|
|
7
|
+
name: 'NexxtIcon',
|
|
8
|
+
template: '<span :data-name="name" :data-type="type" />',
|
|
9
|
+
props: ['name', 'type'],
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const mountItem = (props = {}, slots = {}) =>
|
|
14
|
+
mount(SidebarMenuItem, {
|
|
15
|
+
props: { icon: 'house', ...props },
|
|
16
|
+
slots,
|
|
17
|
+
global: { stubs },
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('SidebarMenuItem', () => {
|
|
21
|
+
describe('rendering', () => {
|
|
22
|
+
it('renders a button element', () => {
|
|
23
|
+
const wrapper = mountItem()
|
|
24
|
+
expect(wrapper.element.tagName).toBe('BUTTON')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('renders the icon', () => {
|
|
28
|
+
const wrapper = mountItem({ icon: 'bell' })
|
|
29
|
+
expect(wrapper.find('[data-name="bell"]').exists()).toBe(true)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('renders slot content', () => {
|
|
33
|
+
const wrapper = mountItem({}, { default: 'Dashboard' })
|
|
34
|
+
expect(wrapper.text()).toContain('Dashboard')
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('notification count', () => {
|
|
39
|
+
it('renders the notification badge when notificationCount is set', () => {
|
|
40
|
+
const wrapper = mountItem({ notificationCount: 3 })
|
|
41
|
+
expect(wrapper.find('span[aria-hidden]').text()).toBe('3')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('does not render the notification badge when notificationCount is absent', () => {
|
|
45
|
+
const wrapper = mountItem()
|
|
46
|
+
expect(wrapper.find('span[class*="bg-red"]').exists()).toBe(false)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('includes the notification count in an sr-only span for screen readers', () => {
|
|
50
|
+
const wrapper = mountItem({ notificationCount: 3 })
|
|
51
|
+
expect(wrapper.find('.sr-only').text()).toContain('3')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('does not render an sr-only span when notificationCount is absent', () => {
|
|
55
|
+
const wrapper = mountItem()
|
|
56
|
+
expect(wrapper.find('.sr-only').exists()).toBe(false)
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe('current route', () => {
|
|
61
|
+
it('applies the active class when isCurentRoute is true', () => {
|
|
62
|
+
const wrapper = mountItem({ isCurentRoute: true })
|
|
63
|
+
expect(wrapper.classes()).toContain('bg-cornflower-blue-300/40')
|
|
64
|
+
expect(wrapper.classes()).toContain('shadow-[inset_4px_0_0_0_#fff]')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('does not apply the active class when isCurentRoute is false', () => {
|
|
68
|
+
const wrapper = mountItem({ isCurentRoute: false })
|
|
69
|
+
expect(wrapper.classes()).not.toContain('shadow-[inset_4px_0_0_0_#fff]')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('sets aria-current="page" when isCurentRoute is true', () => {
|
|
73
|
+
const wrapper = mountItem({ isCurentRoute: true })
|
|
74
|
+
expect(wrapper.attributes('aria-current')).toBe('page')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('omits aria-current when isCurentRoute is false', () => {
|
|
78
|
+
const wrapper = mountItem({ isCurentRoute: false })
|
|
79
|
+
expect(wrapper.attributes('aria-current')).toBeUndefined()
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
describe('variant', () => {
|
|
84
|
+
it('applies cornflower blue hover by default', () => {
|
|
85
|
+
const wrapper = mountItem()
|
|
86
|
+
expect(wrapper.classes()).toContain('hover:bg-cornflower-blue-300/40')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('applies brick red hover for danger variant', () => {
|
|
90
|
+
const wrapper = mountItem({ variant: 'danger' })
|
|
91
|
+
expect(wrapper.classes()).toContain('hover:bg-brick-500')
|
|
92
|
+
expect(wrapper.classes()).not.toContain('hover:bg-cornflower-blue-300/40')
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
describe('click emit', () => {
|
|
97
|
+
it('emits click when the button is clicked', async () => {
|
|
98
|
+
const wrapper = mountItem()
|
|
99
|
+
await wrapper.trigger('click')
|
|
100
|
+
expect(wrapper.emitted('click')).toHaveLength(1)
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
2
|
+
import SkeletonPropertyCard from './SkeletonPropertyCard.vue'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Components/Molecules/Skeleton property card',
|
|
6
|
+
component: SkeletonPropertyCard,
|
|
7
|
+
} satisfies Meta<typeof SkeletonPropertyCard>
|
|
8
|
+
|
|
9
|
+
type Story = StoryObj<typeof SkeletonPropertyCard>
|
|
10
|
+
|
|
11
|
+
export const Default: Story = {}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import SkeletonPropertyCard from './SkeletonPropertyCard.vue'
|
|
4
|
+
|
|
5
|
+
describe('SkeletonPropertyCard', () => {
|
|
6
|
+
it('renders skeleton card with status role', () => {
|
|
7
|
+
const wrapper = mount(SkeletonPropertyCard)
|
|
8
|
+
|
|
9
|
+
expect(wrapper.find('[role="status"]').exists()).toBe(true)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('renders image skeleton placeholder', () => {
|
|
13
|
+
const wrapper = mount(SkeletonPropertyCard)
|
|
14
|
+
|
|
15
|
+
expect(wrapper.find('.h-40.w-full').exists()).toBe(true)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('renders text skeleton placeholders', () => {
|
|
19
|
+
const wrapper = mount(SkeletonPropertyCard)
|
|
20
|
+
const skeletons = wrapper.findAll('.animate-pulse')
|
|
21
|
+
|
|
22
|
+
expect(skeletons.length).toBe(5)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('has default aria-label', () => {
|
|
26
|
+
const wrapper = mount(SkeletonPropertyCard)
|
|
27
|
+
|
|
28
|
+
expect(wrapper.find('[aria-label="Woning wordt geladen..."]').exists()).toBe(true)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('accepts custom aria-label', () => {
|
|
32
|
+
const wrapper = mount(SkeletonPropertyCard, {
|
|
33
|
+
props: { ariaLabel: 'Geen woningen gevonden' },
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
expect(wrapper.find('[aria-label="Geen woningen gevonden"]').exists()).toBe(true)
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
2
|
+
import SocialIcons from './SocialIcons.vue'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Components/Molecules/Social Icons',
|
|
6
|
+
component: SocialIcons,
|
|
7
|
+
} satisfies Meta<typeof SocialIcons>
|
|
8
|
+
|
|
9
|
+
type Story = StoryObj<typeof SocialIcons>
|
|
10
|
+
|
|
11
|
+
export const Default: Story = {
|
|
12
|
+
args: {
|
|
13
|
+
facebook: true,
|
|
14
|
+
instagram: true,
|
|
15
|
+
linkedin: true,
|
|
16
|
+
x: true,
|
|
17
|
+
google: true,
|
|
18
|
+
tiktok: true,
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const Selective: Story = {
|
|
23
|
+
args: {
|
|
24
|
+
facebook: true,
|
|
25
|
+
linkedin: true,
|
|
26
|
+
instagram: true,
|
|
27
|
+
},
|
|
28
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import SocialIcons from './SocialIcons.vue'
|
|
4
|
+
|
|
5
|
+
describe('SocialIcons', () => {
|
|
6
|
+
it('renders nothing when no props are set', () => {
|
|
7
|
+
const wrapper = mount(SocialIcons)
|
|
8
|
+
expect(wrapper.findAll('.rounded-full')).toHaveLength(0)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('renders only the requested icons', () => {
|
|
12
|
+
const wrapper = mount(SocialIcons, {
|
|
13
|
+
props: {
|
|
14
|
+
facebook: true,
|
|
15
|
+
instagram: true,
|
|
16
|
+
},
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const icons = wrapper.findAll('.rounded-full')
|
|
20
|
+
expect(icons).toHaveLength(2)
|
|
21
|
+
|
|
22
|
+
// Check for specific background colors to verify which icons are rendered
|
|
23
|
+
expect(icons[0]!.classes()).toContain('bg-brands-facebook')
|
|
24
|
+
expect(icons[1]!.classes()).toContain('bg-brands-instagram')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('has correct accessibility attributes', () => {
|
|
28
|
+
const wrapper = mount(SocialIcons, {
|
|
29
|
+
props: {
|
|
30
|
+
facebook: true,
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const list = wrapper.find('ul')
|
|
35
|
+
expect(list.exists()).toBe(true)
|
|
36
|
+
expect(list.attributes('aria-label')).toBe('Sociale media kanalen')
|
|
37
|
+
|
|
38
|
+
const item = wrapper.find('li')
|
|
39
|
+
expect(item.exists()).toBe(true)
|
|
40
|
+
expect(item.attributes('aria-label')).toBe('Facebook')
|
|
41
|
+
expect(item.findComponent({ name: 'NexxtIcon' }).attributes('aria-hidden')).toBe('true')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('renders all icons when all props are true', () => {
|
|
45
|
+
const wrapper = mount(SocialIcons, {
|
|
46
|
+
props: {
|
|
47
|
+
facebook: true,
|
|
48
|
+
instagram: true,
|
|
49
|
+
linkedin: true,
|
|
50
|
+
x: true,
|
|
51
|
+
google: true,
|
|
52
|
+
tiktok: true,
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
expect(wrapper.findAll('.rounded-full')).toHaveLength(6)
|
|
57
|
+
})
|
|
58
|
+
})
|
package/src/components/molecules/SocialMediaCustomTemplate/SocialMediaCustomTemplate.stories.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
2
|
+
import SocialMediaCustomTemplate from './SocialMediaCustomTemplate.vue'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Components/Molecules/Social Media Custom Template',
|
|
6
|
+
component: SocialMediaCustomTemplate,
|
|
7
|
+
} satisfies Meta<typeof SocialMediaCustomTemplate>
|
|
8
|
+
|
|
9
|
+
type Story = StoryObj<typeof SocialMediaCustomTemplate>
|
|
10
|
+
|
|
11
|
+
export const Default: Story = {}
|