@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.
Files changed (223) hide show
  1. package/dist/index.js +49 -126
  2. package/dist/nuxt.js +0 -1
  3. package/package.json +38 -36
  4. package/src/attribute-contract.test.ts +151 -0
  5. package/src/components/atoms/AnimatedNumber/AnimatedNumber.stories.ts +36 -0
  6. package/src/components/atoms/AnimatedNumber/AnimatedNumber.test.ts +21 -0
  7. package/src/components/atoms/Avatar/Avatar.stories.ts +122 -0
  8. package/src/components/atoms/Avatar/Avatar.test.ts +134 -0
  9. package/src/components/atoms/Button/Button.stories.ts +290 -0
  10. package/src/components/atoms/Button/Button.test.ts +406 -0
  11. package/src/components/atoms/Card/Card.stories.ts +45 -0
  12. package/src/components/atoms/Card/Card.test.ts +46 -0
  13. package/src/components/atoms/Checkbox/Checkbox.stories.ts +35 -0
  14. package/src/components/atoms/Checkbox/Checkbox.test.ts +55 -0
  15. package/src/components/atoms/Chip/Chip.stories.ts +274 -0
  16. package/src/components/atoms/Chip/Chip.test.ts +230 -0
  17. package/src/components/atoms/EnergyLabel/EnergyLabel.stories.ts +189 -0
  18. package/src/components/atoms/EnergyLabel/EnergyLabel.test.ts +100 -0
  19. package/src/components/atoms/FloatingPanel/FloatingPanel.stories.ts +677 -0
  20. package/src/components/atoms/FloatingPanel/FloatingPanel.test.ts +864 -0
  21. package/src/components/atoms/Icon/Icon.stories.ts +63 -0
  22. package/src/components/atoms/Icon/Icon.test.ts +89 -0
  23. package/src/components/atoms/Loader/Loader.stories.ts +36 -0
  24. package/src/components/atoms/Loader/Loader.test.ts +40 -0
  25. package/src/components/atoms/PickerItem/PickerItem.stories.ts +172 -0
  26. package/src/components/atoms/PickerItem/PickerItem.test.ts +142 -0
  27. package/src/components/atoms/PickerPreview/PickerPreview.stories.ts +183 -0
  28. package/src/components/atoms/PickerPreview/PickerPreview.test.ts +137 -0
  29. package/src/components/atoms/PresenceIndicator/PresenceIndicator.stories.ts +21 -0
  30. package/src/components/atoms/PropertyListing/PropertyListing.stories.ts +38 -0
  31. package/src/components/atoms/PropertyListing/PropertyListing.test.ts +46 -0
  32. package/src/components/atoms/SelectableOption/SelectableOption.stories.ts +131 -0
  33. package/src/components/atoms/SelectableOption/SelectableOption.test.ts +96 -0
  34. package/src/components/atoms/Skeleton/Skeleton.stories.ts +27 -0
  35. package/src/components/atoms/Skeleton/Skeleton.test.ts +28 -0
  36. package/src/components/atoms/Slider/Slider.stories.ts +151 -0
  37. package/src/components/atoms/Slider/Slider.test.ts +191 -0
  38. package/src/components/atoms/Squircle/Squircle.stories.ts +114 -0
  39. package/src/components/atoms/Squircle/Squircle.test.ts +70 -0
  40. package/src/components/atoms/Switch/Switch.stories.ts +91 -0
  41. package/src/components/atoms/Switch/Switch.test.ts +183 -0
  42. package/src/components/atoms/Tooltip/Tooltip.stories.ts +158 -0
  43. package/src/components/atoms/Tooltip/Tooltip.test.ts +134 -0
  44. package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.stories.ts +124 -0
  45. package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.test.ts +464 -0
  46. package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.stories.ts +266 -0
  47. package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.test.ts +421 -0
  48. package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.stories.ts +365 -0
  49. package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.test.ts +484 -0
  50. package/src/components/editor/composables/useRadioGrid.test.ts +261 -0
  51. package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.stories.ts +73 -0
  52. package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.test.ts +189 -0
  53. package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.stories.ts +309 -0
  54. package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.test.ts +359 -0
  55. package/src/components/editor/molecules/EditorAnchor/EditorAnchor.stories.ts +138 -0
  56. package/src/components/editor/molecules/EditorAnchor/EditorAnchor.test.ts +344 -0
  57. package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.stories.ts +87 -0
  58. package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.test.ts +262 -0
  59. package/src/components/editor/molecules/EditorBlur/EditorBlur.stories.ts +115 -0
  60. package/src/components/editor/molecules/EditorBlur/EditorBlur.test.ts +243 -0
  61. package/src/components/editor/molecules/EditorBorder/EditorBorder.stories.ts +187 -0
  62. package/src/components/editor/molecules/EditorBorder/EditorBorder.test.ts +456 -0
  63. package/src/components/editor/molecules/EditorColor/EditorColor.stories.ts +563 -0
  64. package/src/components/editor/molecules/EditorColor/EditorColor.test.ts +689 -0
  65. package/src/components/editor/molecules/EditorColor/EditorColorGradient.test.ts +793 -0
  66. package/src/components/editor/molecules/EditorColor/gradientClass.test.ts +303 -0
  67. package/src/components/editor/molecules/EditorDashPattern/EditorDashPattern.stories.ts +72 -0
  68. package/src/components/editor/molecules/EditorDashPattern/EditorDashPattern.test.ts +124 -0
  69. package/src/components/editor/molecules/EditorFlexDirection/EditorFlexDirection.stories.ts +46 -0
  70. package/src/components/editor/molecules/EditorFlexDirection/EditorFlexDirection.test.ts +97 -0
  71. package/src/components/editor/molecules/EditorFontSize/EditorFontSize.stories.ts +248 -0
  72. package/src/components/editor/molecules/EditorFontSize/EditorFontSize.test.ts +288 -0
  73. package/src/components/editor/molecules/EditorGap/EditorGap.stories.ts +129 -0
  74. package/src/components/editor/molecules/EditorGap/EditorGap.test.ts +253 -0
  75. package/src/components/editor/molecules/EditorHeadingType/EditorHeadingType.stories.ts +68 -0
  76. package/src/components/editor/molecules/EditorHeadingType/EditorHeadingType.test.ts +150 -0
  77. package/src/components/editor/molecules/EditorJustifyContent/EditorJustifyContent.stories.ts +71 -0
  78. package/src/components/editor/molecules/EditorJustifyContent/EditorJustifyContent.test.ts +161 -0
  79. package/src/components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.stories.ts +204 -0
  80. package/src/components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.test.ts +238 -0
  81. package/src/components/editor/molecules/EditorLineHeight/EditorLineHeight.stories.ts +257 -0
  82. package/src/components/editor/molecules/EditorLineHeight/EditorLineHeight.test.ts +286 -0
  83. package/src/components/editor/molecules/EditorLineStyle/EditorLineStyle.stories.ts +135 -0
  84. package/src/components/editor/molecules/EditorLineStyle/EditorLineStyle.test.ts +230 -0
  85. package/src/components/editor/molecules/EditorMaxWidth/EditorMaxWidth.stories.ts +154 -0
  86. package/src/components/editor/molecules/EditorMaxWidth/EditorMaxWidth.test.ts +112 -0
  87. package/src/components/editor/molecules/EditorOverrideStatus/EditorOverrideStatus.stories.ts +206 -0
  88. package/src/components/editor/molecules/EditorOverrideStatus/EditorOverrideStatus.test.ts +332 -0
  89. package/src/components/editor/molecules/EditorPadding/EditorPadding.stories.ts +374 -0
  90. package/src/components/editor/molecules/EditorPadding/EditorPadding.test.ts +712 -0
  91. package/src/components/editor/molecules/EditorRadius/EditorRadius.stories.ts +64 -0
  92. package/src/components/editor/molecules/EditorRadius/EditorRadius.test.ts +137 -0
  93. package/src/components/editor/molecules/EditorShadow/EditorShadow.stories.ts +103 -0
  94. package/src/components/editor/molecules/EditorShadow/EditorShadow.test.ts +168 -0
  95. package/src/components/editor/molecules/EditorSize/EditorSize.stories.ts +84 -0
  96. package/src/components/editor/molecules/EditorSize/EditorSize.test.ts +253 -0
  97. package/src/components/editor/molecules/EditorSizeLimit/EditorSizeLimit.stories.ts +49 -0
  98. package/src/components/editor/molecules/EditorSizeLimit/EditorSizeLimit.test.ts +43 -0
  99. package/src/components/editor/molecules/EditorTextAlign/EditorTextAlign.stories.ts +69 -0
  100. package/src/components/editor/molecules/EditorTextAlign/EditorTextAlign.test.ts +155 -0
  101. package/src/components/molecules/Announcement/Announcement.stories.ts +224 -0
  102. package/src/components/molecules/Announcement/Announcement.test.ts +161 -0
  103. package/src/components/molecules/ContactInfoCard/ContactInfoCard.stories.ts +99 -0
  104. package/src/components/molecules/ContactInfoCard/ContactInfoCard.test.ts +70 -0
  105. package/src/components/molecules/CustomerJourneyTile/CustomerJourneyTile.stories.ts +148 -0
  106. package/src/components/molecules/CustomerJourneyTile/CustomerJourneyTile.test.ts +271 -0
  107. package/src/components/molecules/DoughnutChart/DoughnutChart.stories.ts +45 -0
  108. package/src/components/molecules/DoughnutChart/DoughnutChart.test.ts +205 -0
  109. package/src/components/molecules/InfoBlock/InfoBlock.stories.ts +84 -0
  110. package/src/components/molecules/InfoBlock/InfoBlock.test.ts +101 -0
  111. package/src/components/molecules/InputField/InputField.stories.ts +117 -0
  112. package/src/components/molecules/InputField/InputField.test.ts +138 -0
  113. package/src/components/molecules/InputSelect/InputSelect.stories.ts +116 -0
  114. package/src/components/molecules/InputSelect/InputSelect.test.ts +127 -0
  115. package/src/components/molecules/ModalFooter/ModalFooter.stories.ts +204 -0
  116. package/src/components/molecules/ModalFooter/ModalFooter.test.ts +276 -0
  117. package/src/components/molecules/PageCard/PageCard.stories.ts +100 -0
  118. package/src/components/molecules/PageCard/PageCard.test.ts +91 -0
  119. package/src/components/molecules/PageContainer/PageContainer.stories.ts +90 -0
  120. package/src/components/molecules/PageContainer/PageContainer.test.ts +50 -0
  121. package/src/components/molecules/PageMockup/PageMockup.stories.ts +38 -0
  122. package/src/components/molecules/PageMockup/PageMockup.test.ts +20 -0
  123. package/src/components/molecules/PageTypeTile/PageTypeTile.stories.ts +63 -0
  124. package/src/components/molecules/PageTypeTile/PageTypeTile.test.ts +41 -0
  125. package/src/components/molecules/Pagination/Pagination.stories.ts +72 -0
  126. package/src/components/molecules/Pagination/Pagination.test.ts +130 -0
  127. package/src/components/molecules/PickerGroup/PickerGroup.stories.ts +90 -0
  128. package/src/components/molecules/PickerGroup/PickerGroup.test.ts +145 -0
  129. package/src/components/molecules/PickerList/PickerList.stories.ts +436 -0
  130. package/src/components/molecules/PickerList/PickerList.test.ts +870 -0
  131. package/src/components/molecules/PlaceholderInput/PlaceholderInput.stories.ts +48 -0
  132. package/src/components/molecules/PlaceholderInput/PlaceholderInput.test.ts +107 -0
  133. package/src/components/molecules/PropertyCard/PropertyCard.stories.ts +46 -0
  134. package/src/components/molecules/PropertyCard/PropertyCard.test.ts +59 -0
  135. package/src/components/molecules/Select/Select.stories.ts +264 -0
  136. package/src/components/molecules/Select/Select.test.ts +315 -0
  137. package/src/components/molecules/SidebarMenuItem/SidebarMenuItem.stories.ts +84 -0
  138. package/src/components/molecules/SidebarMenuItem/SidebarMenuItem.test.ts +103 -0
  139. package/src/components/molecules/SkeletonPropertyCard/SkeletonPropertyCard.stories.ts +11 -0
  140. package/src/components/molecules/SkeletonPropertyCard/SkeletonPropertyCard.test.ts +38 -0
  141. package/src/components/molecules/SocialIcons/SocialIcons.stories.ts +28 -0
  142. package/src/components/molecules/SocialIcons/SocialIcons.test.ts +58 -0
  143. package/src/components/molecules/SocialMediaCustomTemplate/SocialMediaCustomTemplate.stories.ts +11 -0
  144. package/src/components/molecules/SocialMediaCustomTemplate/SocialMediaCustomTemplate.test.ts +116 -0
  145. package/src/components/molecules/SocialMediaType/SocialMediaType.stories.ts +37 -0
  146. package/src/components/molecules/SocialMediaType/SocialMediaType.test.ts +126 -0
  147. package/src/components/molecules/Source/Source.stories.ts +35 -0
  148. package/src/components/molecules/Source/Source.test.ts +45 -0
  149. package/src/components/molecules/TabbedContent/TabbedContent.stories.ts +96 -0
  150. package/src/components/molecules/TabbedContent/TabbedContent.test.ts +109 -0
  151. package/src/components/molecules/Tabs/Tabs.stories.ts +230 -0
  152. package/src/components/molecules/Tabs/Tabs.test.ts +280 -0
  153. package/src/components/molecules/TemplateCard/TemplateCard.stories.ts +327 -0
  154. package/src/components/molecules/TemplateCard/TemplateCard.test.ts +339 -0
  155. package/src/components/molecules/Textarea/Textarea.stories.ts +216 -0
  156. package/src/components/molecules/Textarea/Textarea.test.ts +191 -0
  157. package/src/components/molecules/TimelineEvent/TimelineEvent.stories.ts +319 -0
  158. package/src/components/molecules/TimelineEvent/TimelineEvent.test.ts +224 -0
  159. package/src/components/molecules/TimelineLanes/TimelineLanes.stories.ts +29 -0
  160. package/src/components/molecules/TimelineLanes/TimelineLanes.test.ts +30 -0
  161. package/src/components/molecules/Toast/Toast.stories.ts +110 -0
  162. package/src/components/molecules/Toast/Toast.test.ts +103 -0
  163. package/src/components/molecules/ToggleGroup/ToggleGroup.stories.ts +297 -0
  164. package/src/components/molecules/ToggleGroup/ToggleGroup.test.ts +578 -0
  165. package/src/components/molecules/Tree/Tree.stories.ts +884 -0
  166. package/src/components/molecules/Tree/Tree.test.ts +1247 -0
  167. package/src/components/molecules/Tree/tree-drop.test.ts +223 -0
  168. package/src/components/organisms/Calendar/Calendar.stories.ts +108 -0
  169. package/src/components/organisms/Calendar/Calendar.test.ts +279 -0
  170. package/src/components/organisms/Calendar/_CalendarDayView.test.ts +104 -0
  171. package/src/components/organisms/Calendar/_CalendarHeader.test.ts +86 -0
  172. package/src/components/organisms/Calendar/_CalendarMonthView.test.ts +52 -0
  173. package/src/components/organisms/Calendar/_CalendarTimePicker.test.ts +89 -0
  174. package/src/components/organisms/Calendar/_CalendarYearView.test.ts +56 -0
  175. package/src/components/organisms/ColorPicker/ColorPicker.stories.ts +229 -0
  176. package/src/components/organisms/ColorPicker/ColorPicker.test.ts +638 -0
  177. package/src/components/organisms/ColorPicker/gradient.test.ts +67 -0
  178. package/src/components/organisms/ColorPicker/gradientGeometry.test.ts +206 -0
  179. package/src/components/organisms/Combobox/Combobox.stories.ts +234 -0
  180. package/src/components/organisms/Combobox/Combobox.test.ts +512 -0
  181. package/src/components/organisms/Combobox/Combobox.vue +1 -1
  182. package/src/components/organisms/ComboboxPanel/ComboboxPanel.stories.ts +158 -0
  183. package/src/components/organisms/ComboboxPanel/ComboboxPanel.test.ts +524 -0
  184. package/src/components/organisms/ComboboxPanel/ComboboxPanel.vue +1 -1
  185. package/src/components/organisms/CountrySelect/CountrySelect.stories.ts +139 -0
  186. package/src/components/organisms/CountrySelect/CountrySelect.test.ts +149 -0
  187. package/src/components/organisms/DatePicker/DatePicker.stories.ts +163 -0
  188. package/src/components/organisms/DatePicker/DatePicker.test.ts +214 -0
  189. package/src/components/organisms/DropdownButton/DropdownButton.stories.ts +123 -0
  190. package/src/components/organisms/DropdownButton/DropdownButton.test.ts +379 -0
  191. package/src/components/organisms/FontPicker/FontPicker.stories.ts +476 -0
  192. package/src/components/organisms/FontPicker/FontPicker.test.ts +873 -0
  193. package/src/components/organisms/Header/Header.stories.ts +75 -0
  194. package/src/components/organisms/Header/Header.test.ts +233 -0
  195. package/src/components/organisms/IconPicker/IconPicker.stories.ts +734 -0
  196. package/src/components/organisms/IconPicker/IconPicker.test.ts +669 -0
  197. package/src/components/organisms/IconPicker/_IconPickerGrid.test.ts +184 -0
  198. package/src/components/organisms/IconPicker/_IconPickerRail.test.ts +102 -0
  199. package/src/components/organisms/IconPicker/useIconPickerNavigation.test.ts +215 -0
  200. package/src/components/organisms/Modal/Modal.stories.ts +350 -0
  201. package/src/components/organisms/Modal/Modal.test.ts +275 -0
  202. package/src/components/organisms/PickerPanel/PickerPanel.stories.ts +452 -0
  203. package/src/components/organisms/PickerPanel/PickerPanel.test.ts +358 -0
  204. package/src/components/organisms/ProgressBar/ProgressBar.stories.ts +100 -0
  205. package/src/components/organisms/ProgressBar/ProgressBar.test.ts +417 -0
  206. package/src/components/organisms/ReferenceCard/ReferenceCard.stories.ts +191 -0
  207. package/src/components/organisms/ReferenceCard/ReferenceCard.test.ts +165 -0
  208. package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.stories.ts +65 -0
  209. package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.test.ts +466 -0
  210. package/src/components/organisms/StepperHeader/StepperHeader.stories.ts +91 -0
  211. package/src/components/organisms/StepperHeader/StepperHeader.test.ts +279 -0
  212. package/src/components/organisms/Table/Table.stories.ts +332 -0
  213. package/src/components/organisms/Table/Table.test.ts +320 -0
  214. package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.stories.ts +198 -0
  215. package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.test.ts +283 -0
  216. package/src/components/organisms/TimelineTrack/TimelineTrack.stories.ts +120 -0
  217. package/src/components/organisms/TimelineTrack/TimelineTrack.test.ts +91 -0
  218. package/src/components/organisms/Toaster/Toaster.stories.ts +51 -0
  219. package/src/components/organisms/Toaster/Toaster.test.ts +75 -0
  220. package/src/components/organisms/VideoControls/VideoControls.stories.ts +792 -0
  221. package/src/components/organisms/VideoControls/VideoControls.test.ts +1056 -0
  222. package/src/components/organisms/VideoPlayer/VideoPlayer.stories.ts +327 -0
  223. package/src/components/organisms/VideoPlayer/VideoPlayer.test.ts +439 -0
@@ -0,0 +1,191 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import { nextTick } from 'vue'
4
+ import NexxtTextarea from './Textarea.vue'
5
+
6
+ const mountTextarea = (props: Record<string, unknown> = {}) => mount(NexxtTextarea, { props })
7
+
8
+ /**
9
+ * jsdom has no layout engine, so `scrollHeight` is always 0 and `lineHeight`
10
+ * resolves to `normal`. Autosize is measured against the DOM, so the DOM has to
11
+ * be told what to report; stubbing these two is what makes the behaviour
12
+ * observable at all rather than silently passing on zeroes.
13
+ */
14
+ const stubMetrics = (
15
+ el: HTMLTextAreaElement,
16
+ scrollHeight: number,
17
+ { lineHeight = 20, padding = 0, border = 0 } = {},
18
+ ) => {
19
+ Object.defineProperty(el, 'scrollHeight', { configurable: true, value: scrollHeight })
20
+ vi.spyOn(window, 'getComputedStyle').mockReturnValue({
21
+ lineHeight: `${lineHeight}px`,
22
+ paddingTop: `${padding / 2}px`,
23
+ paddingBottom: `${padding / 2}px`,
24
+ borderTopWidth: `${border / 2}px`,
25
+ borderBottomWidth: `${border / 2}px`,
26
+ } as CSSStyleDeclaration)
27
+ }
28
+
29
+ beforeEach(() => vi.restoreAllMocks())
30
+
31
+ describe('NexxtTextarea labelling', () => {
32
+ it('points the label at the textarea it labels', () => {
33
+ const wrapper = mountTextarea({ label: 'Toelichting' })
34
+ expect(wrapper.find('label').attributes('for')).toBe(wrapper.find('textarea').attributes('id'))
35
+ })
36
+
37
+ it('gives each instance its own id, so two on a page do not collide', () => {
38
+ const a = mountTextarea({ label: 'A' })
39
+ const b = mountTextarea({ label: 'B' })
40
+ expect(a.find('textarea').attributes('id')).not.toBe(b.find('textarea').attributes('id'))
41
+ })
42
+
43
+ it('renders no label element when no label is given', () => {
44
+ expect(mountTextarea().find('label').exists()).toBe(false)
45
+ })
46
+
47
+ it('marks a required field on the label', () => {
48
+ expect(mountTextarea({ label: 'A', required: true }).find('label').text()).toContain('*')
49
+ })
50
+ })
51
+
52
+ describe('NexxtTextarea error state', () => {
53
+ it('shows the message only while error is set', () => {
54
+ expect(mountTextarea({ errorMessage: 'Verplicht' }).text()).not.toContain('Verplicht')
55
+ expect(mountTextarea({ error: true, errorMessage: 'Verplicht' }).text()).toContain('Verplicht')
56
+ })
57
+
58
+ it('changes the border on the control, not on the wrapper', () => {
59
+ const normal = mountTextarea()
60
+ const failing = mountTextarea({ error: true })
61
+
62
+ // Which element carries the state is the part worth pinning: styling the
63
+ // wrapper instead would look identical in a screenshot and be wrong.
64
+ expect(failing.find('textarea').classes()).not.toEqual(normal.find('textarea').classes())
65
+ expect(failing.find('div').classes()).toEqual(normal.find('div').classes())
66
+ })
67
+ })
68
+
69
+ describe('NexxtTextarea value', () => {
70
+ it('emits the typed value', async () => {
71
+ const wrapper = mountTextarea()
72
+ await wrapper.find('textarea').setValue('hallo')
73
+ expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['hallo'])
74
+ })
75
+
76
+ it('renders a null value as an empty field, not as the word null', () => {
77
+ // A nullable database column is the normal case for this field, so binding
78
+ // one must not put "null" in front of the user.
79
+ const wrapper = mountTextarea({ modelValue: null })
80
+ expect((wrapper.find('textarea').element as HTMLTextAreaElement).value).toBe('')
81
+ })
82
+
83
+ it('renders the value it is given', () => {
84
+ expect(
85
+ (mountTextarea({ modelValue: 'bestaand' }).find('textarea').element as HTMLTextAreaElement)
86
+ .value,
87
+ ).toBe('bestaand')
88
+ })
89
+ })
90
+
91
+ describe('NexxtTextarea rows and autosize', () => {
92
+ it('uses a fixed row count by default', () => {
93
+ expect(mountTextarea().find('textarea').attributes('rows')).toBe('3')
94
+ expect(mountTextarea({ rows: 6 }).find('textarea').attributes('rows')).toBe('6')
95
+ })
96
+
97
+ it('leaves the height alone when autosize is off', async () => {
98
+ const wrapper = mountTextarea({ rows: 3 })
99
+ const el = wrapper.find('textarea').element as HTMLTextAreaElement
100
+ stubMetrics(el, 400)
101
+
102
+ await wrapper.find('textarea').setValue('een\ntwee\ndrie\nvier')
103
+ await nextTick()
104
+
105
+ expect(el.style.height).toBe('')
106
+ })
107
+
108
+ it('grows to the content height when autosize is on', async () => {
109
+ const wrapper = mountTextarea({ autosize: true })
110
+ const el = wrapper.find('textarea').element as HTMLTextAreaElement
111
+ stubMetrics(el, 84, { border: 2 })
112
+
113
+ await wrapper.find('textarea').setValue('een\ntwee\ndrie')
114
+ await nextTick()
115
+
116
+ // scrollHeight (84) plus the border, because `height` is border-box here.
117
+ expect(el.style.height).toBe('86px')
118
+ })
119
+
120
+ it('stops growing at maxRows and starts scrolling instead', async () => {
121
+ const wrapper = mountTextarea({ autosize: true, maxRows: 4 })
122
+ const el = wrapper.find('textarea').element as HTMLTextAreaElement
123
+ // Content wants 200px; the cap is 4 rows of 20 plus 16 padding and 2 border.
124
+ stubMetrics(el, 200, { lineHeight: 20, padding: 16, border: 2 })
125
+
126
+ await wrapper.find('textarea').setValue('veel regels')
127
+ await nextTick()
128
+
129
+ expect(el.style.height).toBe('98px')
130
+ expect(el.style.overflowY).toBe('auto')
131
+ })
132
+
133
+ it('keeps overflow hidden while still below maxRows', async () => {
134
+ const wrapper = mountTextarea({ autosize: true, maxRows: 10 })
135
+ const el = wrapper.find('textarea').element as HTMLTextAreaElement
136
+ stubMetrics(el, 60, { lineHeight: 20 })
137
+
138
+ await wrapper.find('textarea').setValue('kort')
139
+ await nextTick()
140
+
141
+ expect(el.style.overflowY).toBe('hidden')
142
+ })
143
+
144
+ it('resizes on a programmatic value change, not only on typing', async () => {
145
+ const wrapper = mountTextarea({ autosize: true })
146
+ const el = wrapper.find('textarea').element as HTMLTextAreaElement
147
+ stubMetrics(el, 120)
148
+
149
+ await wrapper.setProps({ modelValue: 'van buitenaf gezet' })
150
+ await nextTick()
151
+
152
+ expect(el.style.height).toBe('120px')
153
+ })
154
+ })
155
+
156
+ describe('NexxtTextarea resize', () => {
157
+ const grip = (props: Record<string, unknown> = {}) => {
158
+ const classes = mountTextarea(props).find('textarea').classes()
159
+ return classes.includes('resize-none') ? 'none' : classes.includes('resize-y') ? 'y' : 'other'
160
+ }
161
+
162
+ it('can be dragged taller by default', () => {
163
+ expect(grip()).toBe('y')
164
+ })
165
+
166
+ it('drops the grip when resize is none', () => {
167
+ expect(grip({ resize: 'none' })).toBe('none')
168
+ })
169
+
170
+ it('lets autosize win over an explicit resize', () => {
171
+ // The autosizer rewrites the height on every keystroke, so a dragged height
172
+ // would not survive the next character. Asking for both is a mistake worth
173
+ // resolving one way rather than rendering a grip that undoes itself.
174
+ expect(grip({ autosize: true, resize: 'vertical' })).toBe('none')
175
+ })
176
+ })
177
+
178
+ describe('NexxtTextarea attribute routing', () => {
179
+ it('puts a consumer class on the wrapper and not on the control', () => {
180
+ const wrapper = mount(NexxtTextarea, { attrs: { class: 'layout-probe' } })
181
+ expect(wrapper.find('div').classes()).toContain('layout-probe')
182
+ expect(wrapper.find('textarea').classes()).not.toContain('layout-probe')
183
+ })
184
+
185
+ it('puts every other attribute on the control and not on the wrapper', () => {
186
+ const wrapper = mount(NexxtTextarea, { attrs: { maxlength: '120', 'data-probe': 'x' } })
187
+ expect(wrapper.find('textarea').attributes('maxlength')).toBe('120')
188
+ expect(wrapper.find('textarea').attributes('data-probe')).toBe('x')
189
+ expect(wrapper.find('div').attributes('data-probe')).toBeUndefined()
190
+ })
191
+ })
@@ -0,0 +1,319 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite'
2
+ import TimelineEvent from './TimelineEvent.vue'
3
+
4
+ export default {
5
+ title: 'Components/Molecules/Timeline Event',
6
+ component: TimelineEvent,
7
+ } satisfies Meta<typeof TimelineEvent>
8
+
9
+ type Story = StoryObj<typeof TimelineEvent>
10
+
11
+ const baseEvent = {
12
+ id: 1,
13
+ happened_at: '2024-06-15T10:30:00.000Z',
14
+ medium_type: null,
15
+ url: null,
16
+ page_title: null,
17
+ request_reason: null,
18
+ agenda_item_type: null,
19
+ source: 'system',
20
+ direction: 'outbound',
21
+ description: null,
22
+ }
23
+
24
+ export const ContactCreated: Story = {
25
+ args: {
26
+ event: {
27
+ ...baseEvent,
28
+ type: 'contact_created',
29
+ },
30
+ splitView: true,
31
+ },
32
+ }
33
+
34
+ export const InboundEvent: Story = {
35
+ args: {
36
+ event: {
37
+ ...baseEvent,
38
+ type: 'contact_requested',
39
+ direction: 'inbound',
40
+ },
41
+ splitView: true,
42
+ },
43
+ }
44
+
45
+ export const PageViewed: Story = {
46
+ args: {
47
+ event: {
48
+ ...baseEvent,
49
+ type: 'page_viewed',
50
+ url: 'https://example.com/page',
51
+ page_title: 'Example Page',
52
+ description: 'Example Page',
53
+ },
54
+ splitView: true,
55
+ },
56
+ }
57
+
58
+ export const WhatsAppMessageReceived: Story = {
59
+ args: {
60
+ event: {
61
+ ...baseEvent,
62
+ type: 'whatsapp_message_received',
63
+ },
64
+ splitView: true,
65
+ },
66
+ }
67
+
68
+ export const WhatsAppMessageSent: Story = {
69
+ args: {
70
+ event: {
71
+ ...baseEvent,
72
+ type: 'whatsapp_message_sent',
73
+ direction: 'outbound',
74
+ },
75
+ splitView: true,
76
+ },
77
+ }
78
+
79
+ export const WithUrl: Story = {
80
+ args: {
81
+ event: {
82
+ ...baseEvent,
83
+ type: 'proposal_sent',
84
+ url: 'https://example.com/proposal/123',
85
+ description: 'Voorstel Q2 2024',
86
+ },
87
+ splitView: true,
88
+ },
89
+ }
90
+
91
+ export const WithRequestReason: Story = {
92
+ args: {
93
+ event: {
94
+ ...baseEvent,
95
+ type: 'house_viewing_requested',
96
+ request_reason: 'Interesse in de woning na open dag',
97
+ },
98
+ splitView: true,
99
+ },
100
+ }
101
+
102
+ export const SplitViewDisabled: Story = {
103
+ args: {
104
+ event: {
105
+ ...baseEvent,
106
+ type: 'contact_created',
107
+ direction: 'inbound',
108
+ },
109
+ splitView: false,
110
+ },
111
+ }
112
+
113
+ export const ViewingAgendaItemScheduled: Story = {
114
+ args: {
115
+ event: {
116
+ ...baseEvent,
117
+ type: 'viewing_agenda_item_scheduled',
118
+ },
119
+ splitView: true,
120
+ },
121
+ }
122
+
123
+ export const ProposalAccepted: Story = {
124
+ args: {
125
+ event: {
126
+ ...baseEvent,
127
+ type: 'proposal_accepted',
128
+ },
129
+ splitView: true,
130
+ },
131
+ }
132
+
133
+ export const KeyHandoverScheduled: Story = {
134
+ args: {
135
+ event: {
136
+ ...baseEvent,
137
+ type: 'key_handover_agenda_item_scheduled',
138
+ },
139
+ splitView: true,
140
+ },
141
+ }
142
+
143
+ export const AutomationPhaseOpened: Story = {
144
+ args: {
145
+ event: { ...baseEvent, type: 'automation_phase_opened' },
146
+ splitView: true,
147
+ },
148
+ }
149
+
150
+ export const AutomationWhatsAppButtonClicked: Story = {
151
+ args: {
152
+ event: { ...baseEvent, type: 'automation_whatsapp_button_clicked' },
153
+ splitView: true,
154
+ },
155
+ }
156
+
157
+ export const AutomationButtonMailAnswered: Story = {
158
+ args: {
159
+ event: { ...baseEvent, type: 'automation_button_mail_answered' },
160
+ splitView: true,
161
+ },
162
+ }
163
+
164
+ export const WaitingListSignup: Story = {
165
+ args: {
166
+ event: { ...baseEvent, type: 'waiting_list_signup' },
167
+ splitView: true,
168
+ },
169
+ }
170
+
171
+ export const ViewingCreated: Story = {
172
+ args: {
173
+ event: { ...baseEvent, type: 'viewing_created' },
174
+ splitView: true,
175
+ },
176
+ }
177
+
178
+ export const GeneralAgendaItemScheduled: Story = {
179
+ args: {
180
+ event: { ...baseEvent, type: 'general_agenda_item_scheduled' },
181
+ splitView: true,
182
+ },
183
+ }
184
+
185
+ export const SalesAgendaItemScheduled: Story = {
186
+ args: {
187
+ event: { ...baseEvent, type: 'sales_agenda_item_scheduled' },
188
+ splitView: true,
189
+ },
190
+ }
191
+
192
+ export const AgendaItemScheduled: Story = {
193
+ args: {
194
+ event: { ...baseEvent, type: 'agenda_item_scheduled' },
195
+ splitView: true,
196
+ },
197
+ }
198
+
199
+ export const AppraisalAgendaItemScheduled: Story = {
200
+ args: {
201
+ event: { ...baseEvent, type: 'appraisal_agenda_item_scheduled' },
202
+ splitView: true,
203
+ },
204
+ }
205
+
206
+ export const PurchaseAgendaItemScheduled: Story = {
207
+ args: {
208
+ event: { ...baseEvent, type: 'purchase_agenda_item_scheduled' },
209
+ splitView: true,
210
+ },
211
+ }
212
+
213
+ export const StructuralInspectionAgendaItemScheduled: Story = {
214
+ args: {
215
+ event: { ...baseEvent, type: 'structural_inspection_agenda_item_scheduled' },
216
+ splitView: true,
217
+ },
218
+ }
219
+
220
+ export const ValuationAgendaItemScheduled: Story = {
221
+ args: {
222
+ event: { ...baseEvent, type: 'valuation_agenda_item_scheduled' },
223
+ splitView: true,
224
+ },
225
+ }
226
+
227
+ export const MortgageAgendaItemScheduled: Story = {
228
+ args: {
229
+ event: { ...baseEvent, type: 'mortgage_agenda_item_scheduled' },
230
+ splitView: true,
231
+ },
232
+ }
233
+
234
+ export const PurchaseDeedSigningAgendaItemScheduled: Story = {
235
+ args: {
236
+ event: { ...baseEvent, type: 'purchase_deed_signing_agenda_item_scheduled' },
237
+ splitView: true,
238
+ },
239
+ }
240
+
241
+ export const ProposalOpened: Story = {
242
+ args: {
243
+ event: { ...baseEvent, type: 'proposal_opened' },
244
+ splitView: true,
245
+ },
246
+ }
247
+
248
+ export const FinalInspectionAgendaItemScheduled: Story = {
249
+ args: {
250
+ event: { ...baseEvent, type: 'final_inspection_agenda_item_scheduled' },
251
+ splitView: true,
252
+ },
253
+ }
254
+
255
+ export const TransportAgendaItemScheduled: Story = {
256
+ args: {
257
+ event: { ...baseEvent, type: 'transport_agenda_item_scheduled' },
258
+ splitView: true,
259
+ },
260
+ }
261
+
262
+ export const NewsletterOpened: Story = {
263
+ args: {
264
+ event: { ...baseEvent, type: 'newsletter_opened' },
265
+ splitView: true,
266
+ },
267
+ }
268
+
269
+ export const AutomationPhaseSent: Story = {
270
+ args: {
271
+ event: { ...baseEvent, type: 'automation_phase_sent' },
272
+ splitView: true,
273
+ },
274
+ }
275
+
276
+ export const AutomationPhaseDelivered: Story = {
277
+ args: {
278
+ event: { ...baseEvent, type: 'automation_phase_delivered' },
279
+ splitView: true,
280
+ },
281
+ }
282
+
283
+ export const NewsletterSent: Story = {
284
+ args: {
285
+ event: { ...baseEvent, type: 'newsletter_sent' },
286
+ splitView: true,
287
+ },
288
+ }
289
+
290
+ export const NewsletterDelivered: Story = {
291
+ args: {
292
+ event: { ...baseEvent, type: 'newsletter_delivered' },
293
+ splitView: true,
294
+ },
295
+ }
296
+
297
+ export const HouseValuationRequested: Story = {
298
+ args: {
299
+ event: { ...baseEvent, type: 'house_valuation_requested' },
300
+ splitView: true,
301
+ },
302
+ }
303
+
304
+ export const Unsubscribed: Story = {
305
+ args: {
306
+ event: { ...baseEvent, type: 'unsubscribed' },
307
+ splitView: true,
308
+ },
309
+ }
310
+
311
+ export const UnknownEventType: Story = {
312
+ args: {
313
+ event: {
314
+ ...baseEvent,
315
+ type: 'custom_event_type',
316
+ },
317
+ splitView: true,
318
+ },
319
+ }