@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,230 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
2
+ import { mount, type VueWrapper } from '@vue/test-utils'
3
+ import EditorLineStyle, {
4
+ EDITOR_LINE_MIN_THICKNESS,
5
+ EDITOR_LINE_STYLE_OPTIONS,
6
+ EDITOR_LINE_THICKNESS_STEPS,
7
+ } from './EditorLineStyle.vue'
8
+ import type { EditorLineStyleValue, EditorLineThickness } from './EditorLineStyle.vue'
9
+ import Slider from '../../../atoms/Slider/Slider.vue'
10
+
11
+ // jsdom ships no ResizeObserver, and reka-ui's slider observes its track.
12
+ vi.stubGlobal(
13
+ 'ResizeObserver',
14
+ class {
15
+ observe() {}
16
+ unobserve() {}
17
+ disconnect() {}
18
+ },
19
+ )
20
+
21
+ // ToggleGroup awaits document.fonts.ready on mount, which jsdom does not ship.
22
+ beforeEach(() => {
23
+ Object.defineProperty(document, 'fonts', {
24
+ value: { ready: Promise.resolve() },
25
+ configurable: true,
26
+ })
27
+ })
28
+
29
+ const render = (
30
+ modelValue: EditorLineStyleValue,
31
+ thickness: EditorLineThickness,
32
+ props: Record<string, unknown> = {},
33
+ ) => mount(EditorLineStyle, { props: { modelValue, thickness, ...props } })
34
+
35
+ /** The option buttons; the slider thumb is a span, so it is not among them. */
36
+ const optionButtons = (wrapper: VueWrapper) => wrapper.findAll('button')
37
+
38
+ const optionNames = (wrapper: VueWrapper) =>
39
+ optionButtons(wrapper).map((button) => button.attributes('aria-label'))
40
+
41
+ const thicknessRow = (wrapper: VueWrapper) =>
42
+ wrapper.get('[data-testid="editor-line-style-thickness"]')
43
+
44
+ const slider = (wrapper: VueWrapper) => wrapper.findComponent(Slider)
45
+
46
+ describe('EditorLineStyle', () => {
47
+ // No assertions on typography utilities: copy is found by structure, text or
48
+ // accessible name, so restyling cannot turn these red.
49
+ it('renders the Dutch default label', () => {
50
+ expect(render('solid', 2).find('p').text()).toBe('Lijn')
51
+ })
52
+
53
+ it('renders a custom label and names the toolbar with it', () => {
54
+ const wrapper = render('solid', 2, { label: 'Line' })
55
+
56
+ expect(wrapper.find('p').text()).toBe('Line')
57
+ expect(wrapper.get('[role="toolbar"]').attributes('aria-label')).toBe('Line')
58
+ })
59
+
60
+ it('names the four options with the Dutch defaults', () => {
61
+ expect(optionNames(render('solid', 2))).toEqual([
62
+ 'Geen',
63
+ 'Doorgetrokken',
64
+ 'Gestreept',
65
+ 'Gestippeld',
66
+ ])
67
+ })
68
+
69
+ it('renders no tooltips, like the other editor toggle groups', () => {
70
+ const wrapper = render('solid', 2)
71
+
72
+ // ToggleGroup only mounts NexxtTooltip -- and with it reka-ui's provider
73
+ // and the trigger's data attribute -- for options that carry a `tooltip`.
74
+ expect(wrapper.findAllComponents({ name: 'TooltipProvider' })).toHaveLength(0)
75
+ expect(wrapper.find('[data-grace-area-trigger]').exists()).toBe(false)
76
+ })
77
+
78
+ it('merges custom messages per key', () => {
79
+ const wrapper = render('solid', 2, { messages: { dashed: 'Dashed', thickness: 'Width' } })
80
+
81
+ expect(optionNames(wrapper)).toEqual(['Geen', 'Doorgetrokken', 'Dashed', 'Gestippeld'])
82
+ expect(wrapper.get('[role="slider"]').attributes('aria-label')).toBe('Width')
83
+ })
84
+
85
+ it('draws the three line styles and shows the ban icon for none', () => {
86
+ const wrapper = render('solid', 2)
87
+ const [none, solid, dashed, dotted] = optionButtons(wrapper)
88
+
89
+ expect(none.find('i').exists()).toBe(true)
90
+ expect(none.find('i').classes().join(' ')).toContain('ban')
91
+ expect(solid.find('span[aria-hidden]').classes()).toContain('border-solid')
92
+ expect(dashed.find('span[aria-hidden]').classes()).toContain('border-dashed')
93
+ expect(dotted.find('span[aria-hidden]').classes()).toContain('border-dotted')
94
+ optionButtons(wrapper).forEach((button) => expect(button.text()).toBe(''))
95
+ })
96
+
97
+ it('leaves the option colours to ToggleGroup', () => {
98
+ const wrapper = render('solid', 2)
99
+
100
+ // No colour of its own on the drawings: they inherit the button's colour,
101
+ // and the active colour in the overlay.
102
+ wrapper
103
+ .findAll('button i, button span[aria-hidden]')
104
+ .forEach((el) => expect(el.classes().some((name) => name.startsWith('text-'))).toBe(false))
105
+ })
106
+
107
+ it('emits the token of the clicked option', async () => {
108
+ const wrapper = render('solid', 2)
109
+
110
+ await optionButtons(wrapper)[2].trigger('click')
111
+
112
+ expect(wrapper.emitted('update:modelValue')).toEqual([['dashed']])
113
+ })
114
+
115
+ describe('thickness row', () => {
116
+ it('is open and reachable while there is a line', () => {
117
+ const row = thicknessRow(render('solid', 2))
118
+
119
+ expect(row.classes()).toContain('grid-rows-[1fr]')
120
+ expect(row.attributes('inert')).toBeUndefined()
121
+ })
122
+
123
+ it('collapses and goes inert for none', () => {
124
+ const row = thicknessRow(render('none', 2))
125
+
126
+ expect(row.classes()).toContain('grid-rows-[0fr]')
127
+ // jsdom reflects the attribute Vue sets as 'true'; presence is what counts.
128
+ expect(row.attributes('inert')).toBeDefined()
129
+ })
130
+
131
+ it('opens again when a style with a line is chosen from outside', async () => {
132
+ const wrapper = render('none', 2)
133
+
134
+ await wrapper.setProps({ modelValue: 'dashed' })
135
+
136
+ expect(thicknessRow(wrapper).attributes('inert')).toBeUndefined()
137
+ })
138
+
139
+ it('renders the heading with the default copy', () => {
140
+ expect(thicknessRow(render('solid', 2)).find('p').text()).toBe('Dikte')
141
+ })
142
+ })
143
+
144
+ describe('slider', () => {
145
+ it('spans every step for solid', () => {
146
+ const wrapper = render('solid', 3)
147
+
148
+ expect(slider(wrapper).props('max')).toBe(EDITOR_LINE_THICKNESS_STEPS.length - 1)
149
+ expect(slider(wrapper).props('modelValue')).toEqual([2])
150
+ expect(wrapper.text()).toContain('3 px')
151
+ })
152
+
153
+ it('names the thumb and announces the thickness in px', () => {
154
+ const thumb = render('solid', 4).get('[role="slider"]')
155
+
156
+ expect(thumb.attributes('aria-label')).toBe('Dikte')
157
+ expect(thumb.attributes('aria-valuetext')).toBe('4 px')
158
+ })
159
+
160
+ it('writes the px of the new step', async () => {
161
+ const wrapper = render('solid', 4)
162
+
163
+ // Index 4 of [1, 2, 3, 4, 6, 8] is 6.
164
+ slider(wrapper).vm.$emit('update:modelValue', [4])
165
+ await wrapper.vm.$nextTick()
166
+
167
+ expect(wrapper.emitted('update:thickness')).toEqual([[6]])
168
+ })
169
+
170
+ it('offers only steps from 2 up for dotted', () => {
171
+ const wrapper = render('dotted', 4)
172
+
173
+ expect(slider(wrapper).props('max')).toBe(4)
174
+ expect(slider(wrapper).props('modelValue')).toEqual([2])
175
+ })
176
+
177
+ it('writes from the dotted list, not from the full one', async () => {
178
+ const wrapper = render('dotted', 2)
179
+
180
+ // Index 1 of [2, 3, 4, 6, 8] is 3; of the full list it would be 2.
181
+ slider(wrapper).vm.$emit('update:modelValue', [1])
182
+ await wrapper.vm.$nextTick()
183
+
184
+ expect(wrapper.emitted('update:thickness')).toEqual([[3]])
185
+ })
186
+
187
+ it('shows an incoming 1 as 2 for dotted without writing it back', () => {
188
+ const wrapper = render('dotted', 1)
189
+
190
+ expect(slider(wrapper).props('modelValue')).toEqual([0])
191
+ expect(wrapper.get('[role="slider"]').attributes('aria-valuetext')).toBe('2 px')
192
+ expect(wrapper.text()).toContain('2 px')
193
+ expect(wrapper.emitted('update:thickness')).toBeUndefined()
194
+ })
195
+
196
+ it('falls back to the first valid step for a value outside the list', () => {
197
+ // The type rules 5 out; a stored value from elsewhere gets past it.
198
+ const offList = 5 as unknown as EditorLineThickness
199
+
200
+ expect(slider(render('solid', offList)).props('modelValue')).toEqual([0])
201
+ expect(render('solid', offList).text()).toContain('1 px')
202
+ expect(render('dotted', offList).text()).toContain('2 px')
203
+ })
204
+
205
+ it('renders a custom unit', () => {
206
+ expect(render('solid', 3, { messages: { unit: 'pixels' } }).text()).toContain('3 pixels')
207
+ })
208
+ })
209
+ })
210
+
211
+ // The consumer derives its matchers and patches from these, so they are a
212
+ // contract: any change here changes what the app recognises.
213
+ describe('EditorLineStyle constants', () => {
214
+ it('offers exactly the agreed styles', () => {
215
+ expect(EDITOR_LINE_STYLE_OPTIONS).toEqual([
216
+ { token: 'none' },
217
+ { token: 'solid' },
218
+ { token: 'dashed' },
219
+ { token: 'dotted' },
220
+ ])
221
+ })
222
+
223
+ it('offers exactly the agreed thickness steps', () => {
224
+ expect(EDITOR_LINE_THICKNESS_STEPS).toEqual([1, 2, 3, 4, 6, 8])
225
+ })
226
+
227
+ it('starts dotted at 2 px and nothing else above the first step', () => {
228
+ expect(EDITOR_LINE_MIN_THICKNESS).toEqual({ dotted: 2 })
229
+ })
230
+ })
@@ -0,0 +1,154 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite'
2
+ import { expect, userEvent, waitFor, within } from 'storybook/test'
3
+ import { ref } from 'vue'
4
+ import EditorMaxWidth from './EditorMaxWidth.vue'
5
+ import type { EditorMaxWidthValue } from './EditorMaxWidth.vue'
6
+
7
+ /**
8
+ * The collapsing wrapper, found by the grid row it animates rather than by any
9
+ * styling class, so restyling the block cannot quietly break the assertions.
10
+ */
11
+ const contentWrapper = (canvasElement: HTMLElement) =>
12
+ canvasElement.querySelector('[class*="grid-rows-"]') as HTMLElement
13
+
14
+ export default {
15
+ title: 'Components/Editor Components/Molecules/Editor Max Width',
16
+ component: EditorMaxWidth,
17
+ decorators: [() => ({ template: '<div class="w-100 mx-auto"><story /></div>' })],
18
+ argTypes: {
19
+ label: { control: 'text' },
20
+ },
21
+ parameters: {
22
+ docs: {
23
+ description: {
24
+ component:
25
+ '**Deprecated** -- use `NexxtEditorSizeLimit` instead: a free maximum in px or % that can be none, rather than the `max-w-*` steps.',
26
+ },
27
+ },
28
+ },
29
+ } satisfies Meta<typeof EditorMaxWidth>
30
+
31
+ type Story = StoryObj<typeof EditorMaxWidth>
32
+
33
+ export const Default: Story = {
34
+ render: (args) => ({
35
+ components: { EditorMaxWidth },
36
+ setup() {
37
+ const maxWidth = ref<EditorMaxWidthValue>('max-w-3xl')
38
+ const enabled = ref(true)
39
+ return { args, maxWidth, enabled }
40
+ },
41
+ // The value is shown as-is: it is the Tailwind class the editor applies.
42
+ template: `
43
+ <div>
44
+ <EditorMaxWidth v-bind="args" v-model="maxWidth" v-model:enabled="enabled" />
45
+ <p class="small-normal text-gray-700">{{ maxWidth }}</p>
46
+ </div>
47
+ `,
48
+ }),
49
+ }
50
+
51
+ /**
52
+ * Switched off means full width: the app writes no `max-w-*` class at all. The
53
+ * chosen width is kept anyway, so switching back on restores it.
54
+ */
55
+ export const SwitchedOff: Story = {
56
+ render: (args) => ({
57
+ components: { EditorMaxWidth },
58
+ setup() {
59
+ const maxWidth = ref<EditorMaxWidthValue>('max-w-3xl')
60
+ const enabled = ref(false)
61
+ return { args, maxWidth, enabled }
62
+ },
63
+ template: `
64
+ <div>
65
+ <EditorMaxWidth v-bind="args" v-model="maxWidth" v-model:enabled="enabled" />
66
+ <p class="small-normal text-gray-700">{{ maxWidth }}</p>
67
+ </div>
68
+ `,
69
+ }),
70
+ play: async ({ canvasElement }) => {
71
+ const canvas = within(canvasElement)
72
+ const content = contentWrapper(canvasElement)
73
+
74
+ expect(content).toHaveAttribute('inert')
75
+
76
+ await userEvent.click(canvas.getByRole('switch'))
77
+ await waitFor(() => expect(content).not.toHaveAttribute('inert'))
78
+
79
+ await userEvent.click(canvas.getByRole('switch'))
80
+ await waitFor(() => expect(content).toHaveAttribute('inert'))
81
+
82
+ // The width survives the round trip: only the switch state changed.
83
+ expect(canvas.getByText('max-w-3xl')).toBeInTheDocument()
84
+ },
85
+ }
86
+
87
+ /** The keyboard reaches both ends of the scale and every step in between. */
88
+ export const AllSteps: Story = {
89
+ render: (args) => ({
90
+ components: { EditorMaxWidth },
91
+ setup() {
92
+ const maxWidth = ref<EditorMaxWidthValue>('max-w-3xl')
93
+ const enabled = ref(true)
94
+ return { args, maxWidth, enabled }
95
+ },
96
+ template: `
97
+ <div>
98
+ <EditorMaxWidth v-bind="args" v-model="maxWidth" v-model:enabled="enabled" />
99
+ <p class="small-normal text-gray-700">{{ maxWidth }}</p>
100
+ </div>
101
+ `,
102
+ }),
103
+ play: async ({ canvasElement }) => {
104
+ const canvas = within(canvasElement)
105
+ const thumb = canvas.getByRole('slider')
106
+
107
+ thumb.focus()
108
+
109
+ await userEvent.keyboard('{End}')
110
+ await waitFor(() => {
111
+ expect(canvas.getByText('1280 px')).toBeInTheDocument()
112
+ expect(canvas.getByText('max-w-7xl')).toBeInTheDocument()
113
+ })
114
+
115
+ await userEvent.keyboard('{Home}')
116
+ await waitFor(() => {
117
+ expect(canvas.getByText('320 px')).toBeInTheDocument()
118
+ expect(canvas.getByText('max-w-xs')).toBeInTheDocument()
119
+ })
120
+
121
+ await userEvent.keyboard('{ArrowRight}')
122
+ await waitFor(() => {
123
+ expect(canvas.getByText('384 px')).toBeInTheDocument()
124
+ expect(canvas.getByText('max-w-sm')).toBeInTheDocument()
125
+ })
126
+ },
127
+ }
128
+
129
+ export const CustomMessages: Story = {
130
+ args: {
131
+ label: 'Max width',
132
+ messages: { unit: 'pt' },
133
+ },
134
+ render: (args) => ({
135
+ components: { EditorMaxWidth },
136
+ setup() {
137
+ const maxWidth = ref<EditorMaxWidthValue>('max-w-3xl')
138
+ const enabled = ref(true)
139
+ return { args, maxWidth, enabled }
140
+ },
141
+ template: `
142
+ <div>
143
+ <EditorMaxWidth v-bind="args" v-model="maxWidth" v-model:enabled="enabled" />
144
+ <p class="small-normal text-gray-700">{{ maxWidth }}</p>
145
+ </div>
146
+ `,
147
+ }),
148
+ play: async ({ canvasElement }) => {
149
+ const canvas = within(canvasElement)
150
+
151
+ expect(canvas.getByRole('slider')).toHaveAttribute('aria-label', 'Max width')
152
+ expect(canvas.getByText('768 pt')).toBeInTheDocument()
153
+ },
154
+ }
@@ -0,0 +1,112 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import EditorMaxWidth, { EDITOR_MAX_WIDTH_STEPS } from './EditorMaxWidth.vue'
4
+ import type { EditorMaxWidthValue } from './EditorMaxWidth.vue'
5
+ import Slider from '../../../atoms/Slider/Slider.vue'
6
+
7
+ // jsdom ships no ResizeObserver, and reka-ui's slider observes its track to place the thumbs
8
+ vi.stubGlobal(
9
+ 'ResizeObserver',
10
+ class {
11
+ observe() {}
12
+ unobserve() {}
13
+ disconnect() {}
14
+ },
15
+ )
16
+
17
+ // jsdom implements no pointer capture either, and reka-ui captures the pointer on thumb pointerdown
18
+ Element.prototype.setPointerCapture ??= () => {}
19
+ Element.prototype.releasePointerCapture ??= () => {}
20
+ Element.prototype.hasPointerCapture ??= () => false
21
+
22
+ /** The index the slider sits on, read from the model handed to the slider. */
23
+ const sliderIndex = (wrapper: ReturnType<typeof mount>) =>
24
+ wrapper.findComponent(Slider).props('modelValue')
25
+
26
+ describe('EditorMaxWidth', () => {
27
+ // No assertions on typography utilities: the label and the value are found by
28
+ // text or by structure, so restyling the block or the slider cannot turn
29
+ // these red.
30
+ it('renders the Dutch default label', () => {
31
+ const wrapper = mount(EditorMaxWidth, { props: { modelValue: 'max-w-3xl' } })
32
+
33
+ expect(wrapper.text()).toContain('Vaste breedte')
34
+ })
35
+
36
+ it('puts the slider on the index of the value and reads it in pixels', () => {
37
+ const wrapper = mount(EditorMaxWidth, { props: { modelValue: 'max-w-3xl' } })
38
+
39
+ expect(sliderIndex(wrapper)).toEqual([6])
40
+ expect(wrapper.find('[role="slider"]').attributes('aria-valuetext')).toBe('768 px')
41
+ expect(wrapper.text()).toContain('768 px')
42
+ })
43
+
44
+ it('spans the whole step list', () => {
45
+ const wrapper = mount(EditorMaxWidth, { props: { modelValue: 'max-w-xs' } })
46
+ const slider = wrapper.findComponent(Slider)
47
+
48
+ expect(slider.props('min')).toBe(0)
49
+ expect(slider.props('max')).toBe(10)
50
+ expect(slider.props('step')).toBe(1)
51
+ })
52
+
53
+ it('emits update:modelValue with the Tailwind class of the new step', async () => {
54
+ const wrapper = mount(EditorMaxWidth, { props: { modelValue: 'max-w-3xl' } })
55
+
56
+ wrapper.findComponent(Slider).vm.$emit('update:modelValue', [10])
57
+ await wrapper.vm.$nextTick()
58
+
59
+ expect(wrapper.emitted('update:modelValue')).toEqual([['max-w-7xl']])
60
+ })
61
+
62
+ it('falls back to the first step for a value outside the scale', () => {
63
+ // `max-w-prose` is a real Tailwind class but not a size on this scale, so
64
+ // there is no step for it to sit on. The type rules it out, which is exactly
65
+ // why the runtime fallback is worth a test: a consumer restoring an old
66
+ // stored value gets past the types.
67
+ const outOfScale = 'max-w-prose' as unknown as EditorMaxWidthValue
68
+ const wrapper = mount(EditorMaxWidth, { props: { modelValue: outOfScale } })
69
+
70
+ expect(sliderIndex(wrapper)).toEqual([0])
71
+ expect(wrapper.text()).toContain('320 px')
72
+ })
73
+
74
+ it('emits update:enabled when the switch is turned off', async () => {
75
+ const wrapper = mount(EditorMaxWidth, { props: { modelValue: 'max-w-3xl', enabled: true } })
76
+
77
+ await wrapper.find('input[type="checkbox"]').setValue(false)
78
+
79
+ expect(wrapper.emitted('update:enabled')).toEqual([[false]])
80
+ })
81
+
82
+ it('renders a custom label and unit', () => {
83
+ const wrapper = mount(EditorMaxWidth, {
84
+ props: { modelValue: 'max-w-3xl', label: 'Max width', messages: { unit: 'pt' } },
85
+ })
86
+
87
+ expect(wrapper.text()).toContain('Max width')
88
+ expect(wrapper.text()).toContain('768 pt')
89
+ expect(wrapper.find('[role="slider"]').attributes('aria-valuetext')).toBe('768 pt')
90
+ expect(wrapper.find('[role="slider"]').attributes('aria-label')).toBe('Max width')
91
+ })
92
+
93
+ it('exposes the step list in ascending order', () => {
94
+ expect(EDITOR_MAX_WIDTH_STEPS).toHaveLength(11)
95
+ expect(EDITOR_MAX_WIDTH_STEPS.map(({ token }) => token)).toEqual([
96
+ 'xs',
97
+ 'sm',
98
+ 'md',
99
+ 'lg',
100
+ 'xl',
101
+ '2xl',
102
+ '3xl',
103
+ '4xl',
104
+ '5xl',
105
+ '6xl',
106
+ '7xl',
107
+ ])
108
+ expect(EDITOR_MAX_WIDTH_STEPS.map(({ px }) => px)).toEqual([
109
+ 320, 384, 448, 512, 576, 672, 768, 896, 1024, 1152, 1280,
110
+ ])
111
+ })
112
+ })