@nexxtmove/ui 2.5.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/dist/components/editor/molecules/EditorBorder/EditorBorder.vue.d.ts +12 -0
  2. package/dist/components/editor/molecules/EditorColor/_ThemeRoleSwatches.vue.d.ts +16 -0
  3. package/dist/index.js +1094 -1135
  4. package/dist/nuxt.js +0 -1
  5. package/package.json +38 -36
  6. package/src/attribute-contract.test.ts +151 -0
  7. package/src/components/atoms/AnimatedNumber/AnimatedNumber.stories.ts +36 -0
  8. package/src/components/atoms/AnimatedNumber/AnimatedNumber.test.ts +21 -0
  9. package/src/components/atoms/Avatar/Avatar.stories.ts +122 -0
  10. package/src/components/atoms/Avatar/Avatar.test.ts +134 -0
  11. package/src/components/atoms/Button/Button.stories.ts +290 -0
  12. package/src/components/atoms/Button/Button.test.ts +406 -0
  13. package/src/components/atoms/Card/Card.stories.ts +45 -0
  14. package/src/components/atoms/Card/Card.test.ts +46 -0
  15. package/src/components/atoms/Checkbox/Checkbox.stories.ts +35 -0
  16. package/src/components/atoms/Checkbox/Checkbox.test.ts +55 -0
  17. package/src/components/atoms/Chip/Chip.stories.ts +274 -0
  18. package/src/components/atoms/Chip/Chip.test.ts +230 -0
  19. package/src/components/atoms/EnergyLabel/EnergyLabel.stories.ts +189 -0
  20. package/src/components/atoms/EnergyLabel/EnergyLabel.test.ts +100 -0
  21. package/src/components/atoms/FloatingPanel/FloatingPanel.stories.ts +677 -0
  22. package/src/components/atoms/FloatingPanel/FloatingPanel.test.ts +864 -0
  23. package/src/components/atoms/Icon/Icon.stories.ts +63 -0
  24. package/src/components/atoms/Icon/Icon.test.ts +89 -0
  25. package/src/components/atoms/Loader/Loader.stories.ts +36 -0
  26. package/src/components/atoms/Loader/Loader.test.ts +40 -0
  27. package/src/components/atoms/PickerItem/PickerItem.stories.ts +172 -0
  28. package/src/components/atoms/PickerItem/PickerItem.test.ts +142 -0
  29. package/src/components/atoms/PickerPreview/PickerPreview.stories.ts +183 -0
  30. package/src/components/atoms/PickerPreview/PickerPreview.test.ts +137 -0
  31. package/src/components/atoms/PresenceIndicator/PresenceIndicator.stories.ts +21 -0
  32. package/src/components/atoms/PropertyListing/PropertyListing.stories.ts +38 -0
  33. package/src/components/atoms/PropertyListing/PropertyListing.test.ts +46 -0
  34. package/src/components/atoms/SelectableOption/SelectableOption.stories.ts +131 -0
  35. package/src/components/atoms/SelectableOption/SelectableOption.test.ts +96 -0
  36. package/src/components/atoms/Skeleton/Skeleton.stories.ts +27 -0
  37. package/src/components/atoms/Skeleton/Skeleton.test.ts +28 -0
  38. package/src/components/atoms/Slider/Slider.stories.ts +151 -0
  39. package/src/components/atoms/Slider/Slider.test.ts +191 -0
  40. package/src/components/atoms/Squircle/Squircle.stories.ts +114 -0
  41. package/src/components/atoms/Squircle/Squircle.test.ts +70 -0
  42. package/src/components/atoms/Switch/Switch.stories.ts +91 -0
  43. package/src/components/atoms/Switch/Switch.test.ts +183 -0
  44. package/src/components/atoms/Tooltip/Tooltip.stories.ts +158 -0
  45. package/src/components/atoms/Tooltip/Tooltip.test.ts +134 -0
  46. package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.stories.ts +124 -0
  47. package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.test.ts +464 -0
  48. package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.stories.ts +266 -0
  49. package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.test.ts +421 -0
  50. package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.stories.ts +365 -0
  51. package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.test.ts +484 -0
  52. package/src/components/editor/composables/useRadioGrid.test.ts +261 -0
  53. package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.stories.ts +73 -0
  54. package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.test.ts +189 -0
  55. package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.stories.ts +309 -0
  56. package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.test.ts +359 -0
  57. package/src/components/editor/molecules/EditorAnchor/EditorAnchor.stories.ts +138 -0
  58. package/src/components/editor/molecules/EditorAnchor/EditorAnchor.test.ts +344 -0
  59. package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.stories.ts +87 -0
  60. package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.test.ts +262 -0
  61. package/src/components/editor/molecules/EditorBlur/EditorBlur.stories.ts +115 -0
  62. package/src/components/editor/molecules/EditorBlur/EditorBlur.test.ts +243 -0
  63. package/src/components/editor/molecules/EditorBorder/EditorBorder.stories.ts +187 -0
  64. package/src/components/editor/molecules/EditorBorder/EditorBorder.test.ts +456 -0
  65. package/src/components/editor/molecules/EditorBorder/EditorBorder.vue +58 -2
  66. package/src/components/editor/molecules/EditorColor/EditorColor.stories.ts +563 -0
  67. package/src/components/editor/molecules/EditorColor/EditorColor.test.ts +689 -0
  68. package/src/components/editor/molecules/EditorColor/EditorColorGradient.test.ts +793 -0
  69. package/src/components/editor/molecules/EditorColor/_ColorField.vue +7 -29
  70. package/src/components/editor/molecules/EditorColor/_ThemeRoleSwatches.vue +57 -0
  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 +638 -0
  182. package/src/components/organisms/ColorPicker/gradient.test.ts +67 -0
  183. package/src/components/organisms/ColorPicker/gradientGeometry.test.ts +206 -0
  184. package/src/components/organisms/Combobox/Combobox.stories.ts +234 -0
  185. package/src/components/organisms/Combobox/Combobox.test.ts +512 -0
  186. package/src/components/organisms/Combobox/Combobox.vue +1 -1
  187. package/src/components/organisms/ComboboxPanel/ComboboxPanel.stories.ts +158 -0
  188. package/src/components/organisms/ComboboxPanel/ComboboxPanel.test.ts +524 -0
  189. package/src/components/organisms/ComboboxPanel/ComboboxPanel.vue +1 -1
  190. package/src/components/organisms/CountrySelect/CountrySelect.stories.ts +139 -0
  191. package/src/components/organisms/CountrySelect/CountrySelect.test.ts +149 -0
  192. package/src/components/organisms/DatePicker/DatePicker.stories.ts +163 -0
  193. package/src/components/organisms/DatePicker/DatePicker.test.ts +214 -0
  194. package/src/components/organisms/DropdownButton/DropdownButton.stories.ts +123 -0
  195. package/src/components/organisms/DropdownButton/DropdownButton.test.ts +379 -0
  196. package/src/components/organisms/FontPicker/FontPicker.stories.ts +476 -0
  197. package/src/components/organisms/FontPicker/FontPicker.test.ts +873 -0
  198. package/src/components/organisms/Header/Header.stories.ts +75 -0
  199. package/src/components/organisms/Header/Header.test.ts +233 -0
  200. package/src/components/organisms/IconPicker/IconPicker.stories.ts +734 -0
  201. package/src/components/organisms/IconPicker/IconPicker.test.ts +669 -0
  202. package/src/components/organisms/IconPicker/_IconPickerGrid.test.ts +184 -0
  203. package/src/components/organisms/IconPicker/_IconPickerRail.test.ts +102 -0
  204. package/src/components/organisms/IconPicker/useIconPickerNavigation.test.ts +215 -0
  205. package/src/components/organisms/Modal/Modal.stories.ts +350 -0
  206. package/src/components/organisms/Modal/Modal.test.ts +275 -0
  207. package/src/components/organisms/PickerPanel/PickerPanel.stories.ts +452 -0
  208. package/src/components/organisms/PickerPanel/PickerPanel.test.ts +358 -0
  209. package/src/components/organisms/ProgressBar/ProgressBar.stories.ts +100 -0
  210. package/src/components/organisms/ProgressBar/ProgressBar.test.ts +417 -0
  211. package/src/components/organisms/ReferenceCard/ReferenceCard.stories.ts +191 -0
  212. package/src/components/organisms/ReferenceCard/ReferenceCard.test.ts +165 -0
  213. package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.stories.ts +65 -0
  214. package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.test.ts +466 -0
  215. package/src/components/organisms/StepperHeader/StepperHeader.stories.ts +91 -0
  216. package/src/components/organisms/StepperHeader/StepperHeader.test.ts +279 -0
  217. package/src/components/organisms/Table/Table.stories.ts +332 -0
  218. package/src/components/organisms/Table/Table.test.ts +320 -0
  219. package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.stories.ts +198 -0
  220. package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.test.ts +283 -0
  221. package/src/components/organisms/TimelineTrack/TimelineTrack.stories.ts +120 -0
  222. package/src/components/organisms/TimelineTrack/TimelineTrack.test.ts +91 -0
  223. package/src/components/organisms/Toaster/Toaster.stories.ts +51 -0
  224. package/src/components/organisms/Toaster/Toaster.test.ts +75 -0
  225. package/src/components/organisms/VideoControls/VideoControls.stories.ts +792 -0
  226. package/src/components/organisms/VideoControls/VideoControls.test.ts +1056 -0
  227. package/src/components/organisms/VideoPlayer/VideoPlayer.stories.ts +327 -0
  228. package/src/components/organisms/VideoPlayer/VideoPlayer.test.ts +439 -0
@@ -0,0 +1,638 @@
1
+ import { describe, it, expect, vi, beforeAll } from 'vitest'
2
+ import { mount, flushPromises } from '@vue/test-utils'
3
+ import { ref, nextTick } from 'vue'
4
+ import NexxtColorPicker from './ColorPicker.vue'
5
+
6
+ // jsdom does not ship ResizeObserver; reka-ui uses it internally (e.g. ColorSwatch useSize)
7
+ beforeAll(() => {
8
+ global.ResizeObserver = class ResizeObserver {
9
+ observe() {}
10
+ unobserve() {}
11
+ disconnect() {}
12
+ }
13
+ })
14
+
15
+ vi.mock('@floating-ui/vue', async (importOriginal) => {
16
+ const actual = await importOriginal<typeof import('@floating-ui/vue')>()
17
+ return {
18
+ ...actual,
19
+ useFloating: () => ({
20
+ floatingStyles: ref({}),
21
+ placement: ref('bottom-start'),
22
+ isPositioned: ref(true),
23
+ }),
24
+ autoUpdate: () => () => {},
25
+ }
26
+ })
27
+
28
+ const open = async (wrapper: ReturnType<typeof mount>) => {
29
+ await wrapper.find('button').trigger('click')
30
+ await flushPromises()
31
+ await nextTick()
32
+ }
33
+
34
+ describe('NexxtColorPicker', () => {
35
+ // ── Rendering ─────────────────────────────────────────────────────────────
36
+
37
+ it('renders with default placeholder', () => {
38
+ const wrapper = mount(NexxtColorPicker)
39
+ expect(wrapper.text()).toContain('Kies kleur...')
40
+ })
41
+
42
+ it('renders a label when the label prop is set', () => {
43
+ const wrapper = mount(NexxtColorPicker, {
44
+ props: { label: 'Merkkleur' },
45
+ })
46
+ expect(wrapper.text()).toContain('Merkkleur')
47
+ })
48
+
49
+ it('renders required asterisk when required is true', () => {
50
+ const wrapper = mount(NexxtColorPicker, {
51
+ props: { label: 'Kleur', required: true },
52
+ })
53
+ const asterisk = wrapper.find('.text-brick-500')
54
+ expect(asterisk.exists()).toBe(true)
55
+ expect(asterisk.text()).toBe('*')
56
+ })
57
+
58
+ it('shows error ring on the trigger when error is true', () => {
59
+ const wrapper = mount(NexxtColorPicker, {
60
+ props: { error: true, errorMessage: 'Kies een kleur' },
61
+ })
62
+ expect(wrapper.find('.ring-brick-400').exists()).toBe(true)
63
+ })
64
+
65
+ it('shows error message when error + errorMessage are set', () => {
66
+ const wrapper = mount(NexxtColorPicker, {
67
+ props: { error: true, errorMessage: 'Kies een kleur om door te gaan' },
68
+ })
69
+ expect(wrapper.text()).toContain('Kies een kleur om door te gaan')
70
+ })
71
+
72
+ it('does not show error message when error is false', () => {
73
+ const wrapper = mount(NexxtColorPicker, {
74
+ props: { error: false, errorMessage: 'Kies een kleur om door te gaan' },
75
+ })
76
+ expect(wrapper.text()).not.toContain('Kies een kleur om door te gaan')
77
+ })
78
+
79
+ // ── modelValue ─────────────────────────────────────────────────────────────
80
+
81
+ it('displays the hex value in the trigger when modelValue is set', () => {
82
+ const wrapper = mount(NexxtColorPicker, {
83
+ props: { modelValue: '#6366f1', formats: ['hex'] },
84
+ })
85
+ expect(wrapper.text()).toContain('#6366f1')
86
+ })
87
+
88
+ it('shows placeholder when modelValue is empty', () => {
89
+ const wrapper = mount(NexxtColorPicker, {
90
+ props: { modelValue: '', placeholder: 'Kies een kleur' },
91
+ })
92
+ expect(wrapper.text()).toContain('Kies een kleur')
93
+ })
94
+ it('shows the value label in the trigger in place of the colour', () => {
95
+ const wrapper = mount(NexxtColorPicker, {
96
+ props: { modelValue: '#6366f1', valueLabel: 'Hoofdkleur' },
97
+ })
98
+
99
+ expect(wrapper.find('button').text()).toBe('Hoofdkleur')
100
+ expect(wrapper.find('[aria-label="Geselecteerde kleur: Hoofdkleur"]').exists()).toBe(true)
101
+ })
102
+
103
+ it('keeps the placeholder over the value label while no colour is set', () => {
104
+ const wrapper = mount(NexxtColorPicker, {
105
+ props: { modelValue: '', valueLabel: 'Hoofdkleur' },
106
+ })
107
+
108
+ expect(wrapper.find('button').text()).toBe('Kies kleur...')
109
+ })
110
+
111
+ it('renders the presets slot in the panel, above the suggestions', async () => {
112
+ const wrapper = mount(NexxtColorPicker, {
113
+ props: { suggestions: { hex: ['#ff0000'] } },
114
+ slots: { presets: '<div data-test="presets">Huisstijl</div>' },
115
+ })
116
+
117
+ expect(wrapper.find('[data-test="presets"]').exists()).toBe(false)
118
+
119
+ await open(wrapper)
120
+
121
+ const presets = wrapper.find('[data-test="presets"]').element
122
+ const suggestion = wrapper.find('button[aria-label="Kies kleur #ff0000"]').element
123
+
124
+ expect(
125
+ presets.compareDocumentPosition(suggestion) & Node.DOCUMENT_POSITION_FOLLOWING,
126
+ ).toBeTruthy()
127
+ })
128
+
129
+ it('shows unique valid colour suggestions only after opening and selects one', async () => {
130
+ const wrapper = mount(NexxtColorPicker, {
131
+ props: { suggestions: { hex: ['#ff0000', '#ff0000', 'not-a-colour'] } },
132
+ })
133
+
134
+ expect(wrapper.findAll('button[aria-label="Kies kleur #ff0000"]')).toHaveLength(0)
135
+
136
+ await open(wrapper)
137
+
138
+ expect(wrapper.findAll('button[aria-label="Kies kleur #ff0000"]')).toHaveLength(1)
139
+ expect(wrapper.text()).toContain('Gebruikte kleuren')
140
+
141
+ await wrapper.find('button[aria-label="Kies kleur #ff0000"]').trigger('click')
142
+
143
+ const updates = wrapper.emitted()['update:modelValue']
144
+ expect(updates?.at(-1)).toEqual(['#ff0000'])
145
+ })
146
+ it('shows suggestions for the active format only', async () => {
147
+ const wrapper = mount(NexxtColorPicker, {
148
+ props: {
149
+ formats: ['hsl', 'hex'],
150
+ suggestions: {
151
+ hex: ['#ff0000'],
152
+ hsl: ['hsl(0, 100%, 50%)'],
153
+ },
154
+ },
155
+ })
156
+
157
+ await open(wrapper)
158
+
159
+ expect(wrapper.find('button[aria-label="Kies kleur hsl(0, 100%, 50%)"]').exists()).toBe(true)
160
+ expect(wrapper.find('button[aria-label="Kies kleur #ff0000"]').exists()).toBe(false)
161
+ })
162
+
163
+ it('rejects alpha input when showAlpha is false', async () => {
164
+ const wrapper = mount(NexxtColorPicker, {
165
+ props: { modelValue: '#3b82f6', formats: ['hex'], showAlpha: false },
166
+ })
167
+ await open(wrapper)
168
+
169
+ const hexInput = wrapper.find('[placeholder="#000000"]')
170
+ await hexInput.setValue('#ff000080')
171
+ await hexInput.trigger('input')
172
+ await nextTick()
173
+
174
+ expect(wrapper.findAll('.ring-brick-400').length).toBeGreaterThan(0)
175
+ expect(wrapper.emitted()['update:modelValue']).toBeUndefined()
176
+ })
177
+
178
+ it('drops the alpha of the current colour when showAlpha is turned off', async () => {
179
+ const wrapper = mount(NexxtColorPicker, { props: { modelValue: '#00aa00e6', showAlpha: true } })
180
+
181
+ await wrapper.setProps({ showAlpha: false })
182
+
183
+ expect(wrapper.emitted('update:modelValue')).toEqual([['#00aa00']])
184
+ })
185
+
186
+ // A wrapper such as EditorColor swaps the value and turns the alpha channel off in
187
+ // one update. `show-alpha` goes first here, so its watcher runs before the
188
+ // model's: it must judge the new value, not the colour it still holds.
189
+ it('leaves a value the parent swaps in while turning showAlpha off', async () => {
190
+ const color = ref('#00aa00e6')
191
+ const alpha = ref(true)
192
+ const wrapper = mount({
193
+ components: { NexxtColorPicker },
194
+ setup: () => ({ color, alpha }),
195
+ template: '<NexxtColorPicker :show-alpha="alpha" v-model="color" />',
196
+ })
197
+
198
+ alpha.value = false
199
+ color.value = '#1777eaff'
200
+ await flushPromises()
201
+
202
+ expect(wrapper.findComponent(NexxtColorPicker).emitted('update:modelValue')).toBeUndefined()
203
+ expect(color.value).toBe('#1777eaff')
204
+ })
205
+
206
+ // ── Accessibility ──────────────────────────────────────────────────────────
207
+
208
+ it('trigger button has aria-expanded=false when closed', () => {
209
+ const wrapper = mount(NexxtColorPicker)
210
+ expect(wrapper.find('button').attributes('aria-expanded')).toBe('false')
211
+ })
212
+
213
+ it('trigger button has aria-expanded=true when open', async () => {
214
+ const wrapper = mount(NexxtColorPicker)
215
+ await open(wrapper)
216
+ expect(wrapper.find('button').attributes('aria-expanded')).toBe('true')
217
+ })
218
+
219
+ it('trigger button has aria-haspopup', () => {
220
+ const wrapper = mount(NexxtColorPicker)
221
+ expect(wrapper.find('button').attributes('aria-haspopup')).toBeTruthy()
222
+ })
223
+
224
+ it('label is linked to trigger button via for/id', () => {
225
+ const wrapper = mount(NexxtColorPicker, {
226
+ props: { label: 'Primaire kleur' },
227
+ })
228
+ const label = wrapper.find('label')
229
+ const btn = wrapper.find('button')
230
+ expect(label.attributes('for')).toBe(btn.attributes('id'))
231
+ })
232
+
233
+ it('trigger has aria-label fallback when no label prop is set', () => {
234
+ const wrapper = mount(NexxtColorPicker, {
235
+ props: { placeholder: 'Kies kleur...' },
236
+ })
237
+ expect(wrapper.find('button').attributes('aria-label')).toBe('Kies kleur...')
238
+ })
239
+
240
+ it('trigger does not have aria-label when label prop is set', () => {
241
+ const wrapper = mount(NexxtColorPicker, {
242
+ props: { label: 'Merkkleur' },
243
+ })
244
+ expect(wrapper.find('button').attributes('aria-label')).toBeUndefined()
245
+ })
246
+
247
+ it('color swatch has a descriptive aria-label with selected color', () => {
248
+ const wrapper = mount(NexxtColorPicker, {
249
+ props: { modelValue: '#6366f1' },
250
+ })
251
+ const swatch = wrapper.find('[aria-label*="Geselecteerde kleur"]')
252
+ expect(swatch.exists()).toBe(true)
253
+ expect(swatch.attributes('aria-label')).toContain('#6366f1')
254
+ })
255
+
256
+ it('color swatch has aria-label indicating no selection when empty', () => {
257
+ const wrapper = mount(NexxtColorPicker, {
258
+ props: { modelValue: '' },
259
+ })
260
+ const swatch = wrapper.find('[aria-label="Geen kleur geselecteerd"]')
261
+ expect(swatch.exists()).toBe(true)
262
+ })
263
+
264
+ // ── Panel & formats ────────────────────────────────────────────────────────
265
+
266
+ it('opens the panel on trigger click', async () => {
267
+ const wrapper = mount(NexxtColorPicker)
268
+ expect(wrapper.find('[placeholder="#000000"]').exists()).toBe(false)
269
+ await open(wrapper)
270
+ expect(wrapper.find('[placeholder="#000000"]').exists()).toBe(true)
271
+ })
272
+
273
+ it('shows HEX input when formats includes hex', async () => {
274
+ const wrapper = mount(NexxtColorPicker, { props: { formats: ['hex'] } })
275
+ await open(wrapper)
276
+ expect(wrapper.find('[placeholder="#000000"]').exists()).toBe(true)
277
+ })
278
+
279
+ it('shows H/S/L inputs when format is hsl', async () => {
280
+ const wrapper = mount(NexxtColorPicker, { props: { formats: ['hsl'] } })
281
+ await open(wrapper)
282
+ expect(wrapper.find('[placeholder="H"]').exists()).toBe(true)
283
+ expect(wrapper.find('[placeholder="S"]').exists()).toBe(true)
284
+ expect(wrapper.find('[placeholder="L"]').exists()).toBe(true)
285
+ })
286
+
287
+ it('shows R/G/B inputs when format is rgb', async () => {
288
+ const wrapper = mount(NexxtColorPicker, { props: { formats: ['rgb'] } })
289
+ await open(wrapper)
290
+ expect(wrapper.find('[placeholder="R"]').exists()).toBe(true)
291
+ expect(wrapper.find('[placeholder="G"]').exists()).toBe(true)
292
+ expect(wrapper.find('[placeholder="B"]').exists()).toBe(true)
293
+ })
294
+
295
+ it('shows format cycle buttons when multiple formats are provided', async () => {
296
+ const wrapper = mount(NexxtColorPicker, {
297
+ props: { formats: ['hex', 'hsl', 'rgb'] },
298
+ })
299
+ await open(wrapper)
300
+ expect(wrapper.find('[aria-label="Vorig formaat"]').exists()).toBe(true)
301
+ expect(wrapper.find('[aria-label="Volgend formaat"]').exists()).toBe(true)
302
+ })
303
+
304
+ it('does not show cycle buttons when only one format is provided', async () => {
305
+ const wrapper = mount(NexxtColorPicker, { props: { formats: ['hex'] } })
306
+ await open(wrapper)
307
+ expect(wrapper.find('[aria-label="Volgend formaat"]').exists()).toBe(false)
308
+ })
309
+
310
+ it('cycles to next format when cycle-down button is clicked', async () => {
311
+ const wrapper = mount(NexxtColorPicker, {
312
+ props: { formats: ['hex', 'hsl'] },
313
+ })
314
+ await open(wrapper)
315
+ expect(wrapper.find('[placeholder="#000000"]').exists()).toBe(true)
316
+ await wrapper.find('[aria-label="Volgend formaat"]').trigger('click')
317
+ await nextTick()
318
+ expect(wrapper.find('[placeholder="H"]').exists()).toBe(true)
319
+ })
320
+
321
+ it('does not show alpha slider by default', async () => {
322
+ const wrapper = mount(NexxtColorPicker, { props: { showAlpha: false } })
323
+ await open(wrapper)
324
+ const sliderThumbs = wrapper.findAll('.cursor-grab.rounded-full.bg-white')
325
+ expect(sliderThumbs.length).toBe(1)
326
+ })
327
+
328
+ it('shows alpha slider when showAlpha is true', async () => {
329
+ const wrapper = mount(NexxtColorPicker, { props: { showAlpha: true } })
330
+ await open(wrapper)
331
+ const sliderThumbs = wrapper.findAll('.cursor-grab.rounded-full.bg-white')
332
+ expect(sliderThumbs.length).toBe(2)
333
+ })
334
+
335
+ // The name is asserted in Chromium as well, in the WithAlpha story.
336
+ it('labels the alpha slider with a visible label', async () => {
337
+ const wrapper = mount(NexxtColorPicker, { props: { showAlpha: true } })
338
+ await open(wrapper)
339
+
340
+ const label = wrapper.findAll('span').find((span) => span.text() === 'Dekking')
341
+ expect(label, 'visible "Dekking" label').toBeDefined()
342
+ const slider = wrapper.find(`[aria-labelledby="${label?.attributes('id')}"]`)
343
+
344
+ expect(slider.attributes('role')).toBe('slider')
345
+ expect(slider.attributes('aria-label')).toBeUndefined()
346
+ })
347
+
348
+ it('takes the alpha label from alphaLabel', async () => {
349
+ const wrapper = mount(NexxtColorPicker, { props: { showAlpha: true, alphaLabel: 'Opacity' } })
350
+ await open(wrapper)
351
+
352
+ expect(wrapper.text()).toContain('Opacity')
353
+ expect(wrapper.text()).not.toContain('Dekking')
354
+ })
355
+
356
+ // `#…80` is 128/255, so reka reports 50.196; only the announcement is rounded.
357
+ it('announces the alpha as a whole number without changing the colour', async () => {
358
+ const wrapper = mount(NexxtColorPicker, { props: { modelValue: '#3b82f680', showAlpha: true } })
359
+ await open(wrapper)
360
+
361
+ const slider = wrapper.find('[role="slider"][aria-labelledby]')
362
+
363
+ expect(slider.attributes('aria-valuenow')).toBe('50')
364
+ expect(slider.attributes('aria-valuetext')).toBe('50%')
365
+ expect(wrapper.emitted('update:modelValue')).toBeUndefined()
366
+ })
367
+
368
+ // ── Hex input live update ──────────────────────────────────────────────────
369
+
370
+ it('updates modelValue immediately on valid hex input', async () => {
371
+ const wrapper = mount(NexxtColorPicker, {
372
+ props: { modelValue: '#3b82f6', formats: ['hex'] },
373
+ })
374
+ await open(wrapper)
375
+ const hexInput = wrapper.find('[placeholder="#000000"]')
376
+ await hexInput.setValue('#ff0000')
377
+ await hexInput.trigger('input')
378
+ await nextTick()
379
+ const emitted = wrapper.emitted()['update:modelValue']
380
+ expect(emitted).toBeTruthy()
381
+ expect((emitted![emitted!.length - 1] as string[])[0]).toBe('#ff0000')
382
+ })
383
+
384
+ it('shows error ring on hex input when value is invalid', async () => {
385
+ const wrapper = mount(NexxtColorPicker, {
386
+ props: { modelValue: '#3b82f6', formats: ['hex'] },
387
+ })
388
+ await open(wrapper)
389
+ const hexInput = wrapper.find('[placeholder="#000000"]')
390
+ await hexInput.setValue('notacolor')
391
+ await hexInput.trigger('input')
392
+ await nextTick()
393
+ // The hex field wrapper should have the brick ring
394
+ const errorRings = wrapper.findAll('.ring-brick-400')
395
+ expect(errorRings.length).toBeGreaterThan(0)
396
+ })
397
+
398
+ it('clears hex error ring when a valid color is entered after an invalid one', async () => {
399
+ const wrapper = mount(NexxtColorPicker, {
400
+ props: { modelValue: '#3b82f6', formats: ['hex'] },
401
+ })
402
+ await open(wrapper)
403
+ const hexInput = wrapper.find('[placeholder="#000000"]')
404
+ await hexInput.setValue('bad')
405
+ await hexInput.trigger('input')
406
+ await nextTick()
407
+ await hexInput.setValue('#00ff00')
408
+ await hexInput.trigger('input')
409
+ await nextTick()
410
+ // No brick rings should remain (error prop is false, hex is now valid)
411
+ expect(wrapper.findAll('.ring-brick-400').length).toBe(0)
412
+ })
413
+
414
+ // ── fullWidth & matchWidth ─────────────────────────────────────────────────
415
+
416
+ it('adds w-full to the outer wrapper when fullWidth is true', () => {
417
+ const wrapper = mount(NexxtColorPicker, { props: { fullWidth: true } })
418
+ expect(wrapper.find('.w-full').exists()).toBe(true)
419
+ })
420
+
421
+ it('does not add w-full to the outer wrapper when fullWidth is false', () => {
422
+ const wrapper = mount(NexxtColorPicker, { props: { fullWidth: false } })
423
+ // The outer div should not have w-full
424
+ const outer = wrapper.find('div')
425
+ expect(outer.classes()).not.toContain('w-full')
426
+ })
427
+
428
+ it('trigger button has w-full class when fullWidth is true', () => {
429
+ const wrapper = mount(NexxtColorPicker, { props: { fullWidth: true } })
430
+ expect(wrapper.find('button').classes()).toContain('w-full')
431
+ })
432
+
433
+ it('trigger button has min-w-60 class when fullWidth is false', () => {
434
+ const wrapper = mount(NexxtColorPicker, { props: { fullWidth: false } })
435
+ expect(wrapper.find('button').classes()).toContain('min-w-60')
436
+ })
437
+
438
+ it('panel content has w-full class when matchWidth is true', async () => {
439
+ const wrapper = mount(NexxtColorPicker, {
440
+ props: { matchWidth: true },
441
+ })
442
+ await open(wrapper)
443
+ // The panel content div switches from w-70 to w-full
444
+ expect(wrapper.find('.w-full').exists()).toBe(true)
445
+ })
446
+
447
+ it('panel content has w-70 class when matchWidth is false', async () => {
448
+ const wrapper = mount(NexxtColorPicker, {
449
+ props: { matchWidth: false },
450
+ })
451
+ await open(wrapper)
452
+ expect(wrapper.find('.w-70').exists()).toBe(true)
453
+ })
454
+
455
+ // ── Gradient mode ──────────────────────────────────────────────────────────
456
+
457
+ describe('gradient', () => {
458
+ beforeAll(() => {
459
+ Object.defineProperty(document, 'fonts', {
460
+ value: { ready: Promise.resolve() },
461
+ configurable: true,
462
+ })
463
+ })
464
+
465
+ it('adds nothing without the gradient prop', async () => {
466
+ const wrapper = mount(NexxtColorPicker, { props: { modelValue: '#6366f1' } })
467
+ await open(wrapper)
468
+ expect(wrapper.find('[role="toolbar"]').exists()).toBe(false)
469
+ expect(wrapper.find('[data-gradient-bar]').exists()).toBe(false)
470
+ })
471
+
472
+ it('offers only the gradient with modes gradient, whatever mode says', async () => {
473
+ const wrapper = mount(NexxtColorPicker, {
474
+ props: { modes: ['gradient'], mode: 'solid', modelValue: '#6366f1' },
475
+ })
476
+ await open(wrapper)
477
+ expect(wrapper.findAll('button').some((b) => b.text() === 'Effen')).toBe(false)
478
+ expect(wrapper.find('[data-gradient-bar]').exists()).toBe(true)
479
+ // A bound mode that disagrees with the one offered fill is corrected once.
480
+ expect(wrapper.emitted('update:mode')).toEqual([['gradient']])
481
+ expect(wrapper.emitted('update:gradientValue')).toBeUndefined()
482
+ })
483
+
484
+ it('emits no mode when it already matches the one offered fill', async () => {
485
+ const gradientOnly = mount(NexxtColorPicker, {
486
+ props: { modes: ['gradient'], mode: 'gradient' },
487
+ })
488
+ const solidOnly = mount(NexxtColorPicker, { props: { modelValue: '#6366f1' } })
489
+ await open(gradientOnly)
490
+ await open(solidOnly)
491
+ expect(gradientOnly.emitted('update:mode')).toBeUndefined()
492
+ expect(solidOnly.emitted('update:mode')).toBeUndefined()
493
+ })
494
+
495
+ it('clamps a selected stop from outside and reports the clamped index', async () => {
496
+ const wrapper = mount(NexxtColorPicker, {
497
+ props: {
498
+ modes: ['gradient'],
499
+ selectedStop: 7,
500
+ gradientValue: {
501
+ type: 'linear',
502
+ angle: 90,
503
+ centerX: 50,
504
+ centerY: 50,
505
+ stops: [
506
+ { color: '#000000', position: 0 },
507
+ { color: '#888888', position: 50 },
508
+ { color: '#ffffff', position: 100 },
509
+ ],
510
+ },
511
+ },
512
+ })
513
+ await open(wrapper)
514
+ expect(wrapper.emitted('update:selectedStop')?.at(-1)).toEqual([2])
515
+ expect(
516
+ wrapper.findAll('[data-gradient-bar] [role="slider"]')[2]?.attributes('data-selected'),
517
+ ).toBeDefined()
518
+
519
+ // A later out-of-range index from outside is clamped and reported back too.
520
+ const before = wrapper.emitted('update:selectedStop')?.length ?? 0
521
+ await wrapper.setProps({ selectedStop: 9 })
522
+ expect(wrapper.emitted('update:selectedStop')).toHaveLength(before + 1)
523
+ expect(wrapper.emitted('update:selectedStop')?.at(-1)).toEqual([2])
524
+ })
525
+
526
+ it('shows gradientStart while there is no gradient, and writes it on the first change', async () => {
527
+ const start = {
528
+ type: 'linear' as const,
529
+ angle: 90,
530
+ centerX: 50,
531
+ centerY: 50,
532
+ stops: [
533
+ { color: '#111111', position: 0 },
534
+ { color: '#22222200', position: 100 },
535
+ ],
536
+ }
537
+ const wrapper = mount(NexxtColorPicker, {
538
+ props: { modes: ['gradient'], gradientStart: start },
539
+ })
540
+ await open(wrapper)
541
+ await wrapper.find('[data-angle-end]').trigger('keydown', { key: 'ArrowUp' })
542
+ expect(wrapper.emitted('update:gradientValue')?.[0]).toEqual([{ ...start, angle: 91 }])
543
+ })
544
+
545
+ it('reports a switch of mode and leaves the colour alone', async () => {
546
+ const wrapper = mount(NexxtColorPicker, {
547
+ props: { modelValue: '#6366f1', modes: ['solid', 'gradient'] },
548
+ })
549
+ await open(wrapper)
550
+ const verloop = wrapper.findAll('button').find((b) => b.text() === 'Verloop')
551
+ await verloop?.trigger('click')
552
+ expect(wrapper.emitted('update:mode')?.[0]).toEqual(['gradient'])
553
+ expect(wrapper.emitted('update:modelValue')).toBeUndefined()
554
+ })
555
+
556
+ it('shows the default without a gradient, and writes it all on the first change', async () => {
557
+ const wrapper = mount(NexxtColorPicker, {
558
+ props: { modes: ['solid', 'gradient'], mode: 'gradient' },
559
+ })
560
+ expect(wrapper.find('button').text()).toContain('Kies kleur...')
561
+ await open(wrapper)
562
+ expect(wrapper.emitted('update:gradientValue')).toBeUndefined()
563
+ await wrapper.find('[data-gradient-bar] [role="slider"]').trigger('keydown', {
564
+ key: 'ArrowRight',
565
+ })
566
+ expect(wrapper.emitted('update:gradientValue')?.[0]).toEqual([
567
+ {
568
+ type: 'linear',
569
+ angle: 180,
570
+ centerX: 50,
571
+ centerY: 50,
572
+ stops: [
573
+ { color: '#00000000', position: 1 },
574
+ { color: '#00000099', position: 100 },
575
+ ],
576
+ },
577
+ ])
578
+ expect(wrapper.emitted('update:modelValue')).toBeUndefined()
579
+ })
580
+
581
+ it('takes its copy from gradientMessages', async () => {
582
+ const wrapper = mount(NexxtColorPicker, {
583
+ props: {
584
+ modes: ['solid', 'gradient'],
585
+ mode: 'gradient',
586
+ gradientMessages: {
587
+ stops: 'Stops',
588
+ addName: 'Add stop',
589
+ add: 'Add',
590
+ swatchEmpty: 'No colour',
591
+ },
592
+ },
593
+ })
594
+ await open(wrapper)
595
+ expect(wrapper.text()).toContain('Stops')
596
+ expect(wrapper.find('button[aria-haspopup] [role="img"]').attributes('aria-label')).toBe(
597
+ 'No colour',
598
+ )
599
+ const add = wrapper.find('button[aria-label="Add stop"]')
600
+ expect(add.text()).toBe('Add')
601
+ expect(wrapper.find('button[aria-label="Kleurstops omdraaien"]').exists()).toBe(true)
602
+ })
603
+
604
+ it('keeps the colour editor out of the panel until a stop opens it', async () => {
605
+ const wrapper = mount(NexxtColorPicker, {
606
+ props: { modes: ['solid', 'gradient'], mode: 'gradient' },
607
+ attachTo: document.body,
608
+ })
609
+ await open(wrapper)
610
+ expect(wrapper.find('input[placeholder="#000000"]').exists()).toBe(false)
611
+ await wrapper.find('button[aria-label="Kleur kiezen"]').trigger('click')
612
+ await flushPromises()
613
+ await nextTick()
614
+ const dialog = wrapper.find('[role="dialog"]')
615
+ expect(dialog.find('input[placeholder="#000000"]').exists()).toBe(true)
616
+ wrapper.unmount()
617
+ })
618
+
619
+ it('emits close when the panel closes', async () => {
620
+ const wrapper = mount(NexxtColorPicker, {
621
+ props: { modes: ['solid', 'gradient'] },
622
+ attachTo: document.body,
623
+ })
624
+ await open(wrapper)
625
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
626
+ await nextTick()
627
+ expect(wrapper.emitted('close')).toHaveLength(1)
628
+ wrapper.unmount()
629
+ })
630
+ })
631
+
632
+ it('names the hex field next to its visible format label', async () => {
633
+ const wrapper = mount(NexxtColorPicker, { props: { modelValue: '#6366f1' } })
634
+ await open(wrapper)
635
+ expect(wrapper.find('input[placeholder="#000000"]').attributes('aria-label')).toBe('Hex')
636
+ expect(wrapper.text()).toContain('HEX')
637
+ })
638
+ })