@nexxtmove/ui 2.6.0 → 2.8.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.
Files changed (229) hide show
  1. package/dist/components/editor/molecules/EditorColor/EditorColor.vue.d.ts +7 -5
  2. package/dist/components/editor/molecules/EditorColor/_ColorField.vue.d.ts +4 -1
  3. package/dist/components/organisms/ColorPicker/ColorPicker.vue.d.ts +3 -1
  4. package/dist/index.js +95 -172
  5. package/dist/nuxt.js +0 -1
  6. package/package.json +38 -36
  7. package/src/attribute-contract.test.ts +151 -0
  8. package/src/components/atoms/AnimatedNumber/AnimatedNumber.stories.ts +36 -0
  9. package/src/components/atoms/AnimatedNumber/AnimatedNumber.test.ts +21 -0
  10. package/src/components/atoms/Avatar/Avatar.stories.ts +122 -0
  11. package/src/components/atoms/Avatar/Avatar.test.ts +134 -0
  12. package/src/components/atoms/Button/Button.stories.ts +290 -0
  13. package/src/components/atoms/Button/Button.test.ts +406 -0
  14. package/src/components/atoms/Card/Card.stories.ts +45 -0
  15. package/src/components/atoms/Card/Card.test.ts +46 -0
  16. package/src/components/atoms/Checkbox/Checkbox.stories.ts +35 -0
  17. package/src/components/atoms/Checkbox/Checkbox.test.ts +55 -0
  18. package/src/components/atoms/Chip/Chip.stories.ts +274 -0
  19. package/src/components/atoms/Chip/Chip.test.ts +230 -0
  20. package/src/components/atoms/EnergyLabel/EnergyLabel.stories.ts +189 -0
  21. package/src/components/atoms/EnergyLabel/EnergyLabel.test.ts +100 -0
  22. package/src/components/atoms/FloatingPanel/FloatingPanel.stories.ts +677 -0
  23. package/src/components/atoms/FloatingPanel/FloatingPanel.test.ts +864 -0
  24. package/src/components/atoms/Icon/Icon.stories.ts +63 -0
  25. package/src/components/atoms/Icon/Icon.test.ts +89 -0
  26. package/src/components/atoms/Loader/Loader.stories.ts +36 -0
  27. package/src/components/atoms/Loader/Loader.test.ts +40 -0
  28. package/src/components/atoms/PickerItem/PickerItem.stories.ts +172 -0
  29. package/src/components/atoms/PickerItem/PickerItem.test.ts +142 -0
  30. package/src/components/atoms/PickerPreview/PickerPreview.stories.ts +183 -0
  31. package/src/components/atoms/PickerPreview/PickerPreview.test.ts +137 -0
  32. package/src/components/atoms/PresenceIndicator/PresenceIndicator.stories.ts +21 -0
  33. package/src/components/atoms/PropertyListing/PropertyListing.stories.ts +38 -0
  34. package/src/components/atoms/PropertyListing/PropertyListing.test.ts +46 -0
  35. package/src/components/atoms/SelectableOption/SelectableOption.stories.ts +131 -0
  36. package/src/components/atoms/SelectableOption/SelectableOption.test.ts +96 -0
  37. package/src/components/atoms/Skeleton/Skeleton.stories.ts +27 -0
  38. package/src/components/atoms/Skeleton/Skeleton.test.ts +28 -0
  39. package/src/components/atoms/Slider/Slider.stories.ts +151 -0
  40. package/src/components/atoms/Slider/Slider.test.ts +191 -0
  41. package/src/components/atoms/Squircle/Squircle.stories.ts +114 -0
  42. package/src/components/atoms/Squircle/Squircle.test.ts +70 -0
  43. package/src/components/atoms/Switch/Switch.stories.ts +91 -0
  44. package/src/components/atoms/Switch/Switch.test.ts +183 -0
  45. package/src/components/atoms/Tooltip/Tooltip.stories.ts +158 -0
  46. package/src/components/atoms/Tooltip/Tooltip.test.ts +134 -0
  47. package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.stories.ts +124 -0
  48. package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.test.ts +464 -0
  49. package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.stories.ts +266 -0
  50. package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.test.ts +421 -0
  51. package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.stories.ts +365 -0
  52. package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.test.ts +484 -0
  53. package/src/components/editor/composables/useRadioGrid.test.ts +261 -0
  54. package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.stories.ts +73 -0
  55. package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.test.ts +189 -0
  56. package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.stories.ts +309 -0
  57. package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.test.ts +359 -0
  58. package/src/components/editor/molecules/EditorAnchor/EditorAnchor.stories.ts +138 -0
  59. package/src/components/editor/molecules/EditorAnchor/EditorAnchor.test.ts +344 -0
  60. package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.stories.ts +87 -0
  61. package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.test.ts +262 -0
  62. package/src/components/editor/molecules/EditorBlur/EditorBlur.stories.ts +115 -0
  63. package/src/components/editor/molecules/EditorBlur/EditorBlur.test.ts +243 -0
  64. package/src/components/editor/molecules/EditorBorder/EditorBorder.stories.ts +187 -0
  65. package/src/components/editor/molecules/EditorBorder/EditorBorder.test.ts +456 -0
  66. package/src/components/editor/molecules/EditorColor/EditorColor.stories.ts +633 -0
  67. package/src/components/editor/molecules/EditorColor/EditorColor.test.ts +776 -0
  68. package/src/components/editor/molecules/EditorColor/EditorColor.vue +7 -5
  69. package/src/components/editor/molecules/EditorColor/EditorColorGradient.test.ts +832 -0
  70. package/src/components/editor/molecules/EditorColor/_ColorField.vue +20 -15
  71. package/src/components/editor/molecules/EditorColor/gradientClass.test.ts +303 -0
  72. package/src/components/editor/molecules/EditorDashPattern/EditorDashPattern.stories.ts +72 -0
  73. package/src/components/editor/molecules/EditorDashPattern/EditorDashPattern.test.ts +124 -0
  74. package/src/components/editor/molecules/EditorFlexDirection/EditorFlexDirection.stories.ts +46 -0
  75. package/src/components/editor/molecules/EditorFlexDirection/EditorFlexDirection.test.ts +97 -0
  76. package/src/components/editor/molecules/EditorFontSize/EditorFontSize.stories.ts +248 -0
  77. package/src/components/editor/molecules/EditorFontSize/EditorFontSize.test.ts +288 -0
  78. package/src/components/editor/molecules/EditorGap/EditorGap.stories.ts +129 -0
  79. package/src/components/editor/molecules/EditorGap/EditorGap.test.ts +253 -0
  80. package/src/components/editor/molecules/EditorHeadingType/EditorHeadingType.stories.ts +68 -0
  81. package/src/components/editor/molecules/EditorHeadingType/EditorHeadingType.test.ts +150 -0
  82. package/src/components/editor/molecules/EditorJustifyContent/EditorJustifyContent.stories.ts +71 -0
  83. package/src/components/editor/molecules/EditorJustifyContent/EditorJustifyContent.test.ts +161 -0
  84. package/src/components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.stories.ts +204 -0
  85. package/src/components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.test.ts +238 -0
  86. package/src/components/editor/molecules/EditorLineHeight/EditorLineHeight.stories.ts +257 -0
  87. package/src/components/editor/molecules/EditorLineHeight/EditorLineHeight.test.ts +286 -0
  88. package/src/components/editor/molecules/EditorLineStyle/EditorLineStyle.stories.ts +135 -0
  89. package/src/components/editor/molecules/EditorLineStyle/EditorLineStyle.test.ts +230 -0
  90. package/src/components/editor/molecules/EditorMaxWidth/EditorMaxWidth.stories.ts +154 -0
  91. package/src/components/editor/molecules/EditorMaxWidth/EditorMaxWidth.test.ts +112 -0
  92. package/src/components/editor/molecules/EditorOverrideStatus/EditorOverrideStatus.stories.ts +206 -0
  93. package/src/components/editor/molecules/EditorOverrideStatus/EditorOverrideStatus.test.ts +332 -0
  94. package/src/components/editor/molecules/EditorPadding/EditorPadding.stories.ts +374 -0
  95. package/src/components/editor/molecules/EditorPadding/EditorPadding.test.ts +712 -0
  96. package/src/components/editor/molecules/EditorRadius/EditorRadius.stories.ts +64 -0
  97. package/src/components/editor/molecules/EditorRadius/EditorRadius.test.ts +137 -0
  98. package/src/components/editor/molecules/EditorShadow/EditorShadow.stories.ts +103 -0
  99. package/src/components/editor/molecules/EditorShadow/EditorShadow.test.ts +168 -0
  100. package/src/components/editor/molecules/EditorSize/EditorSize.stories.ts +84 -0
  101. package/src/components/editor/molecules/EditorSize/EditorSize.test.ts +253 -0
  102. package/src/components/editor/molecules/EditorSizeLimit/EditorSizeLimit.stories.ts +49 -0
  103. package/src/components/editor/molecules/EditorSizeLimit/EditorSizeLimit.test.ts +43 -0
  104. package/src/components/editor/molecules/EditorTextAlign/EditorTextAlign.stories.ts +69 -0
  105. package/src/components/editor/molecules/EditorTextAlign/EditorTextAlign.test.ts +155 -0
  106. package/src/components/molecules/Announcement/Announcement.stories.ts +224 -0
  107. package/src/components/molecules/Announcement/Announcement.test.ts +161 -0
  108. package/src/components/molecules/ContactInfoCard/ContactInfoCard.stories.ts +99 -0
  109. package/src/components/molecules/ContactInfoCard/ContactInfoCard.test.ts +70 -0
  110. package/src/components/molecules/CustomerJourneyTile/CustomerJourneyTile.stories.ts +148 -0
  111. package/src/components/molecules/CustomerJourneyTile/CustomerJourneyTile.test.ts +271 -0
  112. package/src/components/molecules/DoughnutChart/DoughnutChart.stories.ts +45 -0
  113. package/src/components/molecules/DoughnutChart/DoughnutChart.test.ts +205 -0
  114. package/src/components/molecules/InfoBlock/InfoBlock.stories.ts +84 -0
  115. package/src/components/molecules/InfoBlock/InfoBlock.test.ts +101 -0
  116. package/src/components/molecules/InputField/InputField.stories.ts +117 -0
  117. package/src/components/molecules/InputField/InputField.test.ts +138 -0
  118. package/src/components/molecules/InputSelect/InputSelect.stories.ts +116 -0
  119. package/src/components/molecules/InputSelect/InputSelect.test.ts +127 -0
  120. package/src/components/molecules/ModalFooter/ModalFooter.stories.ts +204 -0
  121. package/src/components/molecules/ModalFooter/ModalFooter.test.ts +276 -0
  122. package/src/components/molecules/PageCard/PageCard.stories.ts +100 -0
  123. package/src/components/molecules/PageCard/PageCard.test.ts +91 -0
  124. package/src/components/molecules/PageContainer/PageContainer.stories.ts +90 -0
  125. package/src/components/molecules/PageContainer/PageContainer.test.ts +50 -0
  126. package/src/components/molecules/PageMockup/PageMockup.stories.ts +38 -0
  127. package/src/components/molecules/PageMockup/PageMockup.test.ts +20 -0
  128. package/src/components/molecules/PageTypeTile/PageTypeTile.stories.ts +63 -0
  129. package/src/components/molecules/PageTypeTile/PageTypeTile.test.ts +41 -0
  130. package/src/components/molecules/Pagination/Pagination.stories.ts +72 -0
  131. package/src/components/molecules/Pagination/Pagination.test.ts +130 -0
  132. package/src/components/molecules/PickerGroup/PickerGroup.stories.ts +90 -0
  133. package/src/components/molecules/PickerGroup/PickerGroup.test.ts +145 -0
  134. package/src/components/molecules/PickerList/PickerList.stories.ts +436 -0
  135. package/src/components/molecules/PickerList/PickerList.test.ts +870 -0
  136. package/src/components/molecules/PlaceholderInput/PlaceholderInput.stories.ts +48 -0
  137. package/src/components/molecules/PlaceholderInput/PlaceholderInput.test.ts +107 -0
  138. package/src/components/molecules/PropertyCard/PropertyCard.stories.ts +46 -0
  139. package/src/components/molecules/PropertyCard/PropertyCard.test.ts +59 -0
  140. package/src/components/molecules/Select/Select.stories.ts +264 -0
  141. package/src/components/molecules/Select/Select.test.ts +315 -0
  142. package/src/components/molecules/SidebarMenuItem/SidebarMenuItem.stories.ts +84 -0
  143. package/src/components/molecules/SidebarMenuItem/SidebarMenuItem.test.ts +103 -0
  144. package/src/components/molecules/SkeletonPropertyCard/SkeletonPropertyCard.stories.ts +11 -0
  145. package/src/components/molecules/SkeletonPropertyCard/SkeletonPropertyCard.test.ts +38 -0
  146. package/src/components/molecules/SocialIcons/SocialIcons.stories.ts +28 -0
  147. package/src/components/molecules/SocialIcons/SocialIcons.test.ts +58 -0
  148. package/src/components/molecules/SocialMediaCustomTemplate/SocialMediaCustomTemplate.stories.ts +11 -0
  149. package/src/components/molecules/SocialMediaCustomTemplate/SocialMediaCustomTemplate.test.ts +116 -0
  150. package/src/components/molecules/SocialMediaType/SocialMediaType.stories.ts +37 -0
  151. package/src/components/molecules/SocialMediaType/SocialMediaType.test.ts +126 -0
  152. package/src/components/molecules/Source/Source.stories.ts +35 -0
  153. package/src/components/molecules/Source/Source.test.ts +45 -0
  154. package/src/components/molecules/TabbedContent/TabbedContent.stories.ts +96 -0
  155. package/src/components/molecules/TabbedContent/TabbedContent.test.ts +109 -0
  156. package/src/components/molecules/Tabs/Tabs.stories.ts +230 -0
  157. package/src/components/molecules/Tabs/Tabs.test.ts +280 -0
  158. package/src/components/molecules/TemplateCard/TemplateCard.stories.ts +327 -0
  159. package/src/components/molecules/TemplateCard/TemplateCard.test.ts +339 -0
  160. package/src/components/molecules/Textarea/Textarea.stories.ts +216 -0
  161. package/src/components/molecules/Textarea/Textarea.test.ts +191 -0
  162. package/src/components/molecules/TimelineEvent/TimelineEvent.stories.ts +319 -0
  163. package/src/components/molecules/TimelineEvent/TimelineEvent.test.ts +224 -0
  164. package/src/components/molecules/TimelineLanes/TimelineLanes.stories.ts +29 -0
  165. package/src/components/molecules/TimelineLanes/TimelineLanes.test.ts +30 -0
  166. package/src/components/molecules/Toast/Toast.stories.ts +110 -0
  167. package/src/components/molecules/Toast/Toast.test.ts +103 -0
  168. package/src/components/molecules/ToggleGroup/ToggleGroup.stories.ts +297 -0
  169. package/src/components/molecules/ToggleGroup/ToggleGroup.test.ts +578 -0
  170. package/src/components/molecules/Tree/Tree.stories.ts +884 -0
  171. package/src/components/molecules/Tree/Tree.test.ts +1247 -0
  172. package/src/components/molecules/Tree/tree-drop.test.ts +223 -0
  173. package/src/components/organisms/Calendar/Calendar.stories.ts +108 -0
  174. package/src/components/organisms/Calendar/Calendar.test.ts +279 -0
  175. package/src/components/organisms/Calendar/_CalendarDayView.test.ts +104 -0
  176. package/src/components/organisms/Calendar/_CalendarHeader.test.ts +86 -0
  177. package/src/components/organisms/Calendar/_CalendarMonthView.test.ts +52 -0
  178. package/src/components/organisms/Calendar/_CalendarTimePicker.test.ts +89 -0
  179. package/src/components/organisms/Calendar/_CalendarYearView.test.ts +56 -0
  180. package/src/components/organisms/ColorPicker/ColorPicker.stories.ts +229 -0
  181. package/src/components/organisms/ColorPicker/ColorPicker.test.ts +696 -0
  182. package/src/components/organisms/ColorPicker/ColorPicker.vue +12 -3
  183. package/src/components/organisms/ColorPicker/gradient.test.ts +67 -0
  184. package/src/components/organisms/ColorPicker/gradientGeometry.test.ts +206 -0
  185. package/src/components/organisms/Combobox/Combobox.stories.ts +234 -0
  186. package/src/components/organisms/Combobox/Combobox.test.ts +512 -0
  187. package/src/components/organisms/Combobox/Combobox.vue +1 -1
  188. package/src/components/organisms/ComboboxPanel/ComboboxPanel.stories.ts +158 -0
  189. package/src/components/organisms/ComboboxPanel/ComboboxPanel.test.ts +524 -0
  190. package/src/components/organisms/ComboboxPanel/ComboboxPanel.vue +1 -1
  191. package/src/components/organisms/CountrySelect/CountrySelect.stories.ts +139 -0
  192. package/src/components/organisms/CountrySelect/CountrySelect.test.ts +149 -0
  193. package/src/components/organisms/DatePicker/DatePicker.stories.ts +163 -0
  194. package/src/components/organisms/DatePicker/DatePicker.test.ts +214 -0
  195. package/src/components/organisms/DropdownButton/DropdownButton.stories.ts +123 -0
  196. package/src/components/organisms/DropdownButton/DropdownButton.test.ts +379 -0
  197. package/src/components/organisms/FontPicker/FontPicker.stories.ts +476 -0
  198. package/src/components/organisms/FontPicker/FontPicker.test.ts +873 -0
  199. package/src/components/organisms/Header/Header.stories.ts +75 -0
  200. package/src/components/organisms/Header/Header.test.ts +233 -0
  201. package/src/components/organisms/IconPicker/IconPicker.stories.ts +734 -0
  202. package/src/components/organisms/IconPicker/IconPicker.test.ts +669 -0
  203. package/src/components/organisms/IconPicker/_IconPickerGrid.test.ts +184 -0
  204. package/src/components/organisms/IconPicker/_IconPickerRail.test.ts +102 -0
  205. package/src/components/organisms/IconPicker/useIconPickerNavigation.test.ts +215 -0
  206. package/src/components/organisms/Modal/Modal.stories.ts +350 -0
  207. package/src/components/organisms/Modal/Modal.test.ts +275 -0
  208. package/src/components/organisms/PickerPanel/PickerPanel.stories.ts +452 -0
  209. package/src/components/organisms/PickerPanel/PickerPanel.test.ts +358 -0
  210. package/src/components/organisms/ProgressBar/ProgressBar.stories.ts +100 -0
  211. package/src/components/organisms/ProgressBar/ProgressBar.test.ts +417 -0
  212. package/src/components/organisms/ReferenceCard/ReferenceCard.stories.ts +191 -0
  213. package/src/components/organisms/ReferenceCard/ReferenceCard.test.ts +165 -0
  214. package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.stories.ts +65 -0
  215. package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.test.ts +466 -0
  216. package/src/components/organisms/StepperHeader/StepperHeader.stories.ts +91 -0
  217. package/src/components/organisms/StepperHeader/StepperHeader.test.ts +279 -0
  218. package/src/components/organisms/Table/Table.stories.ts +332 -0
  219. package/src/components/organisms/Table/Table.test.ts +320 -0
  220. package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.stories.ts +198 -0
  221. package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.test.ts +283 -0
  222. package/src/components/organisms/TimelineTrack/TimelineTrack.stories.ts +120 -0
  223. package/src/components/organisms/TimelineTrack/TimelineTrack.test.ts +91 -0
  224. package/src/components/organisms/Toaster/Toaster.stories.ts +51 -0
  225. package/src/components/organisms/Toaster/Toaster.test.ts +75 -0
  226. package/src/components/organisms/VideoControls/VideoControls.stories.ts +792 -0
  227. package/src/components/organisms/VideoControls/VideoControls.test.ts +1056 -0
  228. package/src/components/organisms/VideoPlayer/VideoPlayer.stories.ts +327 -0
  229. package/src/components/organisms/VideoPlayer/VideoPlayer.test.ts +439 -0
@@ -0,0 +1,165 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import ReferenceCard, { type ProposalReference } from './ReferenceCard.vue'
4
+
5
+ const REFERENCE: ProposalReference = {
6
+ index: 1,
7
+ postalCode: '7905BK',
8
+ streetName: 'Klaverkamp',
9
+ houseNumber: 22,
10
+ houseType: 'Tussenwoning',
11
+ city: 'Zwolle',
12
+ mainImageUrl: 'https://example.com/house.webp',
13
+ latitude: 52.5168,
14
+ longitude: 6.083,
15
+ useSurface: 124,
16
+ parcelSurface: 210,
17
+ buildYear: 1998,
18
+ volume: 420,
19
+ energyLabel: 'C',
20
+ garage: null,
21
+ insulation: null,
22
+ shedStorageType: null,
23
+ saleDate: '2024-03-18',
24
+ dateOnOfferSince: null,
25
+ transportDate: null,
26
+ salePrice: '465000',
27
+ indexedSalePrice: null,
28
+ pricePerSquareMeter: null,
29
+ }
30
+
31
+ const normalize = (text: string) => text.replace(/[\u00a0\u202f]/g, ' ')
32
+
33
+ const mountCard = (reference: Partial<ProposalReference> = {}, props = {}) =>
34
+ mount(ReferenceCard, {
35
+ props: { reference: { ...REFERENCE, ...reference }, ...props },
36
+ })
37
+
38
+ describe('ReferenceCard', () => {
39
+ it('renders the address, location, price and sale date', () => {
40
+ const wrapper = mountCard()
41
+
42
+ expect(wrapper.text()).toContain('Klaverkamp 22')
43
+ expect(wrapper.text()).toContain('7905BK Zwolle')
44
+ expect(normalize(wrapper.text())).toContain('€ 465.000')
45
+ expect(wrapper.text()).toContain('18-03-2024')
46
+ })
47
+
48
+ it('renders string and numeric house number extensions after the house number', () => {
49
+ expect(mountCard({ houseNumberExt: 'a' }).text()).toContain('Klaverkamp 22-a')
50
+ expect(mountCard({ houseNumberExt: 2 }).text()).toContain('Klaverkamp 22-2')
51
+ })
52
+
53
+ it('formats the sale price as currency', () => {
54
+ expect(normalize(mountCard({ salePrice: '1485000' }).text())).toContain('€ 1.485.000')
55
+ })
56
+
57
+ it('parses Dutch thousands separators in sale prices', () => {
58
+ expect(normalize(mountCard({ salePrice: '383.877' }).text())).toContain('€ 383.877')
59
+ })
60
+
61
+ it('supports another locale and currency', () => {
62
+ const wrapper = mountCard({}, { locale: 'en-US', currency: 'USD' })
63
+
64
+ expect(normalize(wrapper.text())).toContain('$465,000')
65
+ })
66
+
67
+ it('falls back to the raw sale price when it is not a number', () => {
68
+ expect(mountCard({ salePrice: 'op aanvraag' }).text()).toContain('op aanvraag')
69
+ })
70
+
71
+ it('renders the image when provided', () => {
72
+ const img = mountCard().find('img')
73
+
74
+ expect(img.exists()).toBe(true)
75
+ expect(img.attributes('src')).toBe('https://example.com/house.webp')
76
+ expect(img.attributes('alt')).toBe('Klaverkamp 22')
77
+ })
78
+
79
+ it('renders a placeholder when the image is missing', () => {
80
+ const wrapper = mountCard({ mainImageUrl: null })
81
+
82
+ expect(wrapper.find('img').exists()).toBe(false)
83
+ expect(wrapper.find('.fa-house').exists()).toBe(true)
84
+ })
85
+
86
+ it('renders the energy label only when present', () => {
87
+ expect(mountCard().text()).toContain('Energielabel')
88
+ expect(mountCard({ energyLabel: null }).text()).not.toContain('Energielabel')
89
+ })
90
+
91
+ it('omits the price column entirely when there is no price', () => {
92
+ const wrapper = mountCard({ salePrice: null })
93
+
94
+ expect(wrapper.text()).not.toContain('€')
95
+ expect(wrapper.text()).not.toContain('18-03-2024')
96
+ })
97
+
98
+ it('falls back to the raw sale date when it is unparsable', () => {
99
+ expect(mountCard({ saleDate: 'onbekend' }).text()).toContain('onbekend')
100
+ })
101
+
102
+ it('toggles selection when the card is clicked', async () => {
103
+ const wrapper = mountCard()
104
+
105
+ await wrapper.trigger('click')
106
+ expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([true])
107
+
108
+ await wrapper.setProps({ modelValue: true })
109
+ await wrapper.trigger('click')
110
+ expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([false])
111
+ })
112
+
113
+ it('toggles selection with the keyboard', async () => {
114
+ const wrapper = mountCard()
115
+
116
+ await wrapper.trigger('keydown.space')
117
+ expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([true])
118
+
119
+ await wrapper.setProps({ modelValue: true })
120
+ await wrapper.trigger('keydown.enter')
121
+ expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([false])
122
+ })
123
+
124
+ it('reflects the selected state in aria-checked and the label', async () => {
125
+ const wrapper = mountCard({}, { modelValue: false })
126
+
127
+ expect(wrapper.attributes('aria-checked')).toBe('false')
128
+ expect(wrapper.text()).toContain('Selecteer deze woning')
129
+
130
+ await wrapper.setProps({ modelValue: true })
131
+
132
+ expect(wrapper.attributes('aria-checked')).toBe('true')
133
+ expect(wrapper.text()).toContain('Meegenomen in advies')
134
+ })
135
+
136
+ it('emits details with the reference without toggling selection', async () => {
137
+ const wrapper = mountCard()
138
+
139
+ await wrapper.find('button').trigger('click')
140
+
141
+ expect(wrapper.emitted('details')?.[0]).toEqual([REFERENCE])
142
+ expect(wrapper.emitted('update:modelValue')).toBeUndefined()
143
+ })
144
+
145
+ it('merges custom messages with the defaults', () => {
146
+ const wrapper = mountCard(
147
+ {},
148
+ {
149
+ modelValue: true,
150
+ messages: { selected: 'Gekozen', details: 'Details', energyLabel: 'Label' },
151
+ },
152
+ )
153
+
154
+ expect(wrapper.text()).toContain('Gekozen')
155
+ expect(wrapper.text()).toContain('Label')
156
+ expect(wrapper.find('button').text()).toBe('Details')
157
+ })
158
+
159
+ it('keeps the default message for keys that are not overridden', () => {
160
+ const wrapper = mountCard({}, { messages: { details: 'Details' } })
161
+
162
+ expect(wrapper.text()).toContain('Selecteer deze woning')
163
+ expect(wrapper.text()).toContain('Energielabel')
164
+ })
165
+ })
@@ -0,0 +1,65 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite'
2
+ import SocialMediaTemplate from './SocialMediaTemplate.vue'
3
+
4
+ export default {
5
+ title: 'Components/Organisms/Social Media Template',
6
+ component: SocialMediaTemplate,
7
+ argTypes: {
8
+ variant: {
9
+ control: 'select',
10
+ options: ['post', 'video', 'slideshow'],
11
+ },
12
+ selected: {
13
+ control: 'boolean',
14
+ },
15
+ muted: {
16
+ control: 'boolean',
17
+ },
18
+ },
19
+ } satisfies Meta<typeof SocialMediaTemplate>
20
+
21
+ type Story = StoryObj<typeof SocialMediaTemplate>
22
+
23
+ export const Post: Story = {
24
+ args: {
25
+ variant: 'post',
26
+ media: 'https://loremflickr.com/400/600/house',
27
+ mediaType: 'image',
28
+ name: 'Amsterdam',
29
+ type: 'Woning',
30
+ selected: false,
31
+ },
32
+ }
33
+
34
+ export const Video: Story = {
35
+ args: {
36
+ variant: 'video',
37
+ media: '/barcelona.mp4',
38
+ mediaType: 'video',
39
+ muted: true,
40
+ name: 'Barcelona',
41
+ type: 'Woning',
42
+ selected: false,
43
+ },
44
+ }
45
+
46
+ export const Slideshow: Story = {
47
+ args: {
48
+ variant: 'slideshow',
49
+ media: 'https://images.unsplash.com/photo-1551963831-b3b1ca40c98e',
50
+ mediaType: 'image',
51
+ name: 'Stockholm',
52
+ type: 'Bedrijfsunit',
53
+ },
54
+ }
55
+
56
+ export const Selected: Story = {
57
+ args: {
58
+ variant: 'post',
59
+ media: 'https://images.unsplash.com/photo-1551963831-b3b1ca40c98e',
60
+ mediaType: 'image',
61
+ name: 'Amsterdam',
62
+ type: 'Woning',
63
+ selected: true,
64
+ },
65
+ }
@@ -0,0 +1,466 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import { describe, it, expect, vi } from 'vitest'
3
+ import SocialMediaTemplate from './SocialMediaTemplate.vue'
4
+
5
+ describe('SocialMediaTemplate', () => {
6
+ it('renders an image when media is an image file', () => {
7
+ const wrapper = mount(SocialMediaTemplate, {
8
+ props: {
9
+ variant: 'post',
10
+ media: 'https://example.com/image.jpg',
11
+ mediaType: 'image',
12
+ name: 'Amsterdam',
13
+ type: 'Woning',
14
+ },
15
+ })
16
+
17
+ expect(wrapper.find('img').exists()).toBe(true)
18
+ expect(wrapper.find('video').exists()).toBe(false)
19
+ })
20
+
21
+ it('renders a video when media is a video file', () => {
22
+ const wrapper = mount(SocialMediaTemplate, {
23
+ props: {
24
+ variant: 'video',
25
+ media: '/barcelona.mp4',
26
+ mediaType: 'video',
27
+ name: 'Barcelona',
28
+ type: 'Woning',
29
+ muted: true,
30
+ },
31
+ })
32
+
33
+ expect(wrapper.find('video').exists()).toBe(true)
34
+ expect(wrapper.find('img').exists()).toBe(false)
35
+ })
36
+
37
+ it('displays the name and type correctly', () => {
38
+ const wrapper = mount(SocialMediaTemplate, {
39
+ props: {
40
+ variant: 'post',
41
+ media: 'https://example.com/image.png',
42
+ mediaType: 'image',
43
+ name: 'Amsterdam',
44
+ type: 'Woning',
45
+ },
46
+ })
47
+
48
+ expect(wrapper.text()).toContain('Amsterdam')
49
+ expect(wrapper.text()).toContain('Woning')
50
+ })
51
+
52
+ it('shows play icon when variant is video', () => {
53
+ const wrapper = mount(SocialMediaTemplate, {
54
+ props: {
55
+ variant: 'video',
56
+ media: '/test.mp4',
57
+ mediaType: 'video',
58
+ name: 'Test',
59
+ type: 'Video',
60
+ },
61
+ })
62
+
63
+ const icons = wrapper.findAllComponents({ name: 'NexxtIcon' })
64
+ const playIcon = icons.find((icon) => icon.props('name') === 'play-circle')
65
+ expect(playIcon).toBeDefined()
66
+ })
67
+
68
+ it('does not show play icon when variant is post', () => {
69
+ const wrapper = mount(SocialMediaTemplate, {
70
+ props: {
71
+ variant: 'post',
72
+ media: '/test.jpg',
73
+ mediaType: 'image',
74
+ name: 'Test',
75
+ type: 'Image',
76
+ },
77
+ })
78
+
79
+ const icons = wrapper.findAllComponents({ name: 'NexxtIcon' })
80
+ const playIcon = icons.find((icon) => icon.props('name') === 'play')
81
+ expect(playIcon).toBeUndefined()
82
+ })
83
+
84
+ it('shows selected overlay when selected prop is true', () => {
85
+ const wrapper = mount(SocialMediaTemplate, {
86
+ props: {
87
+ variant: 'post',
88
+ media: '/test.jpg',
89
+ mediaType: 'image',
90
+ name: 'Test',
91
+ type: 'Image',
92
+ selected: true,
93
+ },
94
+ })
95
+
96
+ const overlay = wrapper.find('.bg-cornflower-blue-200\\/70')
97
+ expect(overlay.classes()).toContain('opacity-100')
98
+ })
99
+
100
+ it('hides selected overlay when selected prop is false', () => {
101
+ const wrapper = mount(SocialMediaTemplate, {
102
+ props: {
103
+ variant: 'post',
104
+ media: '/test.jpg',
105
+ mediaType: 'image',
106
+ name: 'Test',
107
+ type: 'Image',
108
+ selected: false,
109
+ },
110
+ })
111
+
112
+ const overlay = wrapper.find('.bg-cornflower-blue-200\\/70')
113
+ expect(overlay.classes()).toContain('opacity-0')
114
+ })
115
+
116
+ it('plays video on mouseenter when media is video', async () => {
117
+ const wrapper = mount(SocialMediaTemplate, {
118
+ props: {
119
+ variant: 'video',
120
+ media: '/test.mp4',
121
+ mediaType: 'video',
122
+ name: 'Test',
123
+ type: 'Video',
124
+ muted: true,
125
+ },
126
+ })
127
+
128
+ const video = wrapper.find('video').element as HTMLVideoElement
129
+ const playSpy = vi.spyOn(video, 'play').mockImplementation(() => Promise.resolve())
130
+
131
+ await wrapper.trigger('mouseenter')
132
+
133
+ expect(playSpy).toHaveBeenCalled()
134
+ })
135
+
136
+ it('pauses video on mouseleave when media is video', async () => {
137
+ const wrapper = mount(SocialMediaTemplate, {
138
+ props: {
139
+ variant: 'video',
140
+ media: '/test.mp4',
141
+ mediaType: 'video',
142
+ name: 'Test',
143
+ type: 'Video',
144
+ muted: true,
145
+ },
146
+ })
147
+
148
+ const video = wrapper.find('video').element as HTMLVideoElement
149
+ const pauseSpy = vi.spyOn(video, 'pause')
150
+
151
+ await wrapper.trigger('mouseleave')
152
+
153
+ expect(pauseSpy).toHaveBeenCalled()
154
+ })
155
+
156
+ it('applies aspect-9/16 class when variant is video', () => {
157
+ const wrapper = mount(SocialMediaTemplate, {
158
+ props: {
159
+ variant: 'video',
160
+ media: '/test.mp4',
161
+ mediaType: 'video',
162
+ name: 'Test',
163
+ type: 'Video',
164
+ },
165
+ })
166
+
167
+ expect(wrapper.classes()).toContain('aspect-9/16')
168
+ })
169
+
170
+ it('applies h-80 class when variant is post', () => {
171
+ const wrapper = mount(SocialMediaTemplate, {
172
+ props: {
173
+ variant: 'post',
174
+ media: '/test.jpg',
175
+ mediaType: 'image',
176
+ name: 'Test',
177
+ type: 'Image',
178
+ },
179
+ })
180
+
181
+ expect(wrapper.classes()).toContain('h-80')
182
+ })
183
+
184
+ it('uses media_type prop for image rendering', () => {
185
+ const wrapper = mount(SocialMediaTemplate, {
186
+ props: {
187
+ variant: 'post',
188
+ media: '/test-without-extension',
189
+ mediaType: 'image',
190
+ name: 'Test',
191
+ type: 'Image',
192
+ },
193
+ })
194
+
195
+ expect(wrapper.find('img').exists()).toBe(true)
196
+ expect(wrapper.find('video').exists()).toBe(false)
197
+ })
198
+
199
+ it('uses media_type prop for video rendering', () => {
200
+ const wrapper = mount(SocialMediaTemplate, {
201
+ props: {
202
+ variant: 'video',
203
+ media: '/test-video-without-extension',
204
+ mediaType: 'video',
205
+ name: 'Test',
206
+ type: 'Video',
207
+ muted: true,
208
+ },
209
+ })
210
+
211
+ expect(wrapper.find('video').exists()).toBe(true)
212
+ expect(wrapper.find('img').exists()).toBe(false)
213
+ })
214
+
215
+ it('correctly map various media extensions to mime types', () => {
216
+ const extensions = {
217
+ mp4: 'video/mp4',
218
+ webm: 'video/webm',
219
+ ogg: 'video/ogg',
220
+ mov: 'video/quicktime',
221
+ avi: 'video/x-msvideo',
222
+ mkv: 'video/x-matroska',
223
+ unknown: 'video/mp4',
224
+ }
225
+
226
+ Object.entries(extensions).forEach(([ext, expectedMime]) => {
227
+ const wrapper = mount(SocialMediaTemplate, {
228
+ props: {
229
+ variant: 'video',
230
+ media: `/test.${ext}`,
231
+ mediaType: 'video',
232
+ mediaExtension: ext === 'unknown' ? undefined : ext,
233
+ name: 'Test',
234
+ type: 'Video',
235
+ },
236
+ })
237
+
238
+ const source = wrapper.find('source')
239
+ expect(source.attributes('type')).toBe(expectedMime)
240
+ })
241
+ })
242
+
243
+ it('handles custom aspect ratio correctly', () => {
244
+ const wrapper = mount(SocialMediaTemplate, {
245
+ props: {
246
+ variant: 'video',
247
+ media: '/test.mp4',
248
+ mediaType: 'video',
249
+ name: 'Test',
250
+ type: 'Video',
251
+ aspectRatio: '16:9',
252
+ },
253
+ })
254
+
255
+ expect(wrapper.classes()).toContain('aspect-16/9')
256
+ })
257
+
258
+ it('applies video-specific colors when variant is video', () => {
259
+ const wrapper = mount(SocialMediaTemplate, {
260
+ props: {
261
+ variant: 'video',
262
+ media: '/test.mp4',
263
+ mediaType: 'video',
264
+ name: 'Test',
265
+ type: 'Video',
266
+ selected: true,
267
+ },
268
+ })
269
+
270
+ // Check overlay background
271
+ const overlay = wrapper.find('.z-10')
272
+ expect(overlay.classes()).toContain('bg-purple-200/70')
273
+
274
+ // Check checkmark background
275
+ const checkmarkCircle = overlay.find('.rounded-full')
276
+ expect(checkmarkCircle.classes()).toContain('bg-purple-500')
277
+
278
+ // Check info block background
279
+ const infoBlock = wrapper.find('.z-20')
280
+ expect(infoBlock.classes()).toContain('bg-purple-500')
281
+ })
282
+
283
+ it('uses correct icons based on variant', () => {
284
+ const variants = [
285
+ { variant: 'post', icon: 'image' },
286
+ { variant: 'video', icon: 'play-circle' },
287
+ { variant: 'slideshow', icon: 'images' },
288
+ ]
289
+
290
+ variants.forEach(({ variant, icon }) => {
291
+ const wrapper = mount(SocialMediaTemplate, {
292
+ props: {
293
+ variant: variant as 'post' | 'video' | 'slideshow',
294
+ media: '/test.jpg',
295
+ mediaType: 'image',
296
+ name: 'Test',
297
+ type: 'Test',
298
+ },
299
+ })
300
+ const nexxtIcon = wrapper.find('.z-20').findComponent({ name: 'NexxtIcon' })
301
+ expect(nexxtIcon.props('name')).toBe(icon)
302
+ })
303
+ })
304
+
305
+ it('shows image-stack icon when variant is slideshow', () => {
306
+ const wrapper = mount(SocialMediaTemplate, {
307
+ props: {
308
+ variant: 'slideshow',
309
+ media: '/test.jpg',
310
+ mediaType: 'image',
311
+ name: 'Test',
312
+ type: 'Slideshow',
313
+ },
314
+ })
315
+
316
+ const icons = wrapper.findAllComponents({ name: 'NexxtIcon' })
317
+ const stackIcon = icons.find((icon) => icon.props('name') === 'images')
318
+ expect(stackIcon).toBeDefined()
319
+ })
320
+
321
+ it('correctly uses parsedAspectRatio for slideshow variant', () => {
322
+ const wrapper = mount(SocialMediaTemplate, {
323
+ props: {
324
+ variant: 'slideshow',
325
+ media: '/test.jpg',
326
+ mediaType: 'image',
327
+ name: 'Test',
328
+ type: 'Slideshow',
329
+ aspectRatio: '1:1',
330
+ },
331
+ })
332
+
333
+ expect(wrapper.classes()).toContain('aspect-1/1')
334
+ })
335
+
336
+ it('correctly returns empty string aspect ratio when mediaType is image and no aspect ratio is provided', () => {
337
+ const wrapper = mount(SocialMediaTemplate, {
338
+ props: {
339
+ variant: 'video', // TriggerparsedAspectRatio evaluation
340
+ media: '/test.jpg',
341
+ mediaType: 'image',
342
+ name: 'Test',
343
+ type: 'Test',
344
+ },
345
+ })
346
+
347
+ // Classes should not include any aspect ratio class
348
+ const classes = wrapper.classes()
349
+ expect(classes.some((c) => c.startsWith('aspect-'))).toBe(false)
350
+ })
351
+
352
+ it('computes undefined mime type when mediaType is image', () => {
353
+ const wrapper = mount(SocialMediaTemplate, {
354
+ props: {
355
+ variant: 'post',
356
+ media: 'test.jpg',
357
+ mediaType: 'image',
358
+ name: 'Test',
359
+ type: 'Test',
360
+ },
361
+ })
362
+
363
+ // @ts-expect-error - testing computed property internal
364
+ expect(wrapper.vm.videoMimeType).toBeUndefined()
365
+ })
366
+
367
+ it('handles custom aspect ratio with aspect- prefix correctly', () => {
368
+ const wrapper = mount(SocialMediaTemplate, {
369
+ props: {
370
+ variant: 'video',
371
+ media: '/test.mp4',
372
+ mediaType: 'video',
373
+ name: 'Test',
374
+ type: 'Video',
375
+ aspectRatio: 'aspect-4:3',
376
+ },
377
+ })
378
+
379
+ expect(wrapper.classes()).toContain('aspect-4/3')
380
+ })
381
+
382
+ it('emits click event on click', async () => {
383
+ const wrapper = mount(SocialMediaTemplate, {
384
+ props: {
385
+ variant: 'post',
386
+ media: '/test.jpg',
387
+ mediaType: 'image',
388
+ name: 'Test',
389
+ type: 'Test',
390
+ },
391
+ })
392
+
393
+ await wrapper.trigger('click')
394
+ expect(wrapper.emitted('click')).toBeTruthy()
395
+ })
396
+
397
+ it('emits click event on enter key down', async () => {
398
+ const wrapper = mount(SocialMediaTemplate, {
399
+ props: {
400
+ variant: 'post',
401
+ media: '/test.jpg',
402
+ mediaType: 'image',
403
+ name: 'Test',
404
+ type: 'Test',
405
+ },
406
+ })
407
+
408
+ await wrapper.trigger('keydown.enter')
409
+ expect(wrapper.emitted('click')).toBeTruthy()
410
+ })
411
+
412
+ it('emits click event on space key down', async () => {
413
+ const wrapper = mount(SocialMediaTemplate, {
414
+ props: {
415
+ variant: 'post',
416
+ media: '/test.jpg',
417
+ mediaType: 'image',
418
+ name: 'Test',
419
+ type: 'Test',
420
+ },
421
+ })
422
+
423
+ await wrapper.trigger('keydown.space')
424
+ expect(wrapper.emitted('click')).toBeTruthy()
425
+ })
426
+
427
+ it('plays video on focus when media is video', async () => {
428
+ const wrapper = mount(SocialMediaTemplate, {
429
+ props: {
430
+ variant: 'video',
431
+ media: '/test.mp4',
432
+ mediaType: 'video',
433
+ name: 'Test',
434
+ type: 'Video',
435
+ muted: true,
436
+ },
437
+ })
438
+
439
+ const video = wrapper.find('video').element as HTMLVideoElement
440
+ const playSpy = vi.spyOn(video, 'play').mockImplementation(() => Promise.resolve())
441
+
442
+ await wrapper.trigger('focus')
443
+
444
+ expect(playSpy).toHaveBeenCalled()
445
+ })
446
+
447
+ it('pauses video on blur when media is video', async () => {
448
+ const wrapper = mount(SocialMediaTemplate, {
449
+ props: {
450
+ variant: 'video',
451
+ media: '/test.mp4',
452
+ mediaType: 'video',
453
+ name: 'Test',
454
+ type: 'Video',
455
+ muted: true,
456
+ },
457
+ })
458
+
459
+ const video = wrapper.find('video').element as HTMLVideoElement
460
+ const pauseSpy = vi.spyOn(video, 'pause')
461
+
462
+ await wrapper.trigger('blur')
463
+
464
+ expect(pauseSpy).toHaveBeenCalled()
465
+ })
466
+ })