@wordpress/components 32.4.1-next.v.202603102151.0 → 32.5.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/CHANGELOG.md +19 -1
  2. package/build/alignment-matrix-control/cell.cjs +2 -2
  3. package/build/alignment-matrix-control/cell.cjs.map +1 -1
  4. package/build/alignment-matrix-control/index.cjs +2 -2
  5. package/build/alignment-matrix-control/index.cjs.map +1 -1
  6. package/build/angle-picker-control/angle-circle.cjs +2 -2
  7. package/build/angle-picker-control/angle-circle.cjs.map +1 -1
  8. package/build/combobox-control/index.cjs +5 -1
  9. package/build/combobox-control/index.cjs.map +2 -2
  10. package/build/custom-gradient-picker/index.cjs +9 -1
  11. package/build/custom-gradient-picker/index.cjs.map +2 -2
  12. package/build/date-time/time/index.cjs +1 -1
  13. package/build/date-time/time/index.cjs.map +2 -2
  14. package/build/date-time/utils.cjs +9 -0
  15. package/build/date-time/utils.cjs.map +2 -2
  16. package/build/form-token-field/token-input.cjs +2 -1
  17. package/build/form-token-field/token-input.cjs.map +2 -2
  18. package/build/radio-control/index.cjs +1 -0
  19. package/build/radio-control/index.cjs.map +2 -2
  20. package/build/toggle-group-control/toggle-group-control/as-button-group.cjs +1 -0
  21. package/build/toggle-group-control/toggle-group-control/as-button-group.cjs.map +2 -2
  22. package/build/toggle-group-control/toggle-group-control/component.cjs +15 -9
  23. package/build/toggle-group-control/toggle-group-control/component.cjs.map +3 -3
  24. package/build/toggle-group-control/toggle-group-control/styles.cjs +6 -32
  25. package/build/toggle-group-control/toggle-group-control/styles.cjs.map +3 -3
  26. package/build/validated-form-controls/control-with-error.cjs +26 -3
  27. package/build/validated-form-controls/control-with-error.cjs.map +2 -2
  28. package/build/validated-form-controls/validity-indicator.cjs +2 -0
  29. package/build/validated-form-controls/validity-indicator.cjs.map +2 -2
  30. package/build-module/alignment-matrix-control/cell.mjs +2 -2
  31. package/build-module/alignment-matrix-control/cell.mjs.map +1 -1
  32. package/build-module/alignment-matrix-control/index.mjs +2 -2
  33. package/build-module/alignment-matrix-control/index.mjs.map +1 -1
  34. package/build-module/angle-picker-control/angle-circle.mjs +2 -2
  35. package/build-module/angle-picker-control/angle-circle.mjs.map +1 -1
  36. package/build-module/combobox-control/index.mjs +5 -1
  37. package/build-module/combobox-control/index.mjs.map +2 -2
  38. package/build-module/custom-gradient-picker/index.mjs +10 -2
  39. package/build-module/custom-gradient-picker/index.mjs.map +2 -2
  40. package/build-module/date-time/time/index.mjs +2 -2
  41. package/build-module/date-time/time/index.mjs.map +2 -2
  42. package/build-module/date-time/utils.mjs +8 -0
  43. package/build-module/date-time/utils.mjs.map +2 -2
  44. package/build-module/form-token-field/token-input.mjs +2 -1
  45. package/build-module/form-token-field/token-input.mjs.map +2 -2
  46. package/build-module/radio-control/index.mjs +1 -0
  47. package/build-module/radio-control/index.mjs.map +2 -2
  48. package/build-module/toggle-group-control/toggle-group-control/as-button-group.mjs +1 -0
  49. package/build-module/toggle-group-control/toggle-group-control/as-button-group.mjs.map +2 -2
  50. package/build-module/toggle-group-control/toggle-group-control/component.mjs +17 -11
  51. package/build-module/toggle-group-control/toggle-group-control/component.mjs.map +2 -2
  52. package/build-module/toggle-group-control/toggle-group-control/styles.mjs +6 -21
  53. package/build-module/toggle-group-control/toggle-group-control/styles.mjs.map +2 -2
  54. package/build-module/validated-form-controls/control-with-error.mjs +27 -4
  55. package/build-module/validated-form-controls/control-with-error.mjs.map +2 -2
  56. package/build-module/validated-form-controls/validity-indicator.mjs +2 -0
  57. package/build-module/validated-form-controls/validity-indicator.mjs.map +2 -2
  58. package/build-style/style-rtl.css +14 -8
  59. package/build-style/style.css +14 -8
  60. package/build-types/alignment-matrix-control/stories/index.story.d.ts +1 -1
  61. package/build-types/alignment-matrix-control/stories/index.story.d.ts.map +1 -1
  62. package/build-types/angle-picker-control/stories/index.story.d.ts +1 -1
  63. package/build-types/animate/stories/index.story.d.ts +7 -7
  64. package/build-types/animate/stories/index.story.d.ts.map +1 -1
  65. package/build-types/base-control/stories/index.story.d.ts +1 -1
  66. package/build-types/border-box-control/stories/index.story.d.ts +1 -1
  67. package/build-types/border-control/stories/index.story.d.ts +5 -5
  68. package/build-types/box-control/stories/index.story.d.ts +7 -7
  69. package/build-types/box-control/stories/index.story.d.ts.map +1 -1
  70. package/build-types/button/stories/e2e/index.story.d.ts +1 -1
  71. package/build-types/button/stories/e2e/index.story.d.ts.map +1 -1
  72. package/build-types/button/stories/index.story.d.ts +7 -7
  73. package/build-types/button/stories/index.story.d.ts.map +1 -1
  74. package/build-types/circular-option-picker/stories/index.story.d.ts +5 -5
  75. package/build-types/circular-option-picker/stories/index.story.d.ts.map +1 -1
  76. package/build-types/combobox-control/index.d.ts.map +1 -1
  77. package/build-types/combobox-control/stories/index.story.d.ts +4 -4
  78. package/build-types/combobox-control/stories/index.story.d.ts.map +1 -1
  79. package/build-types/confirm-dialog/stories/index.story.d.ts +2 -2
  80. package/build-types/confirm-dialog/stories/index.story.d.ts.map +1 -1
  81. package/build-types/custom-gradient-picker/index.d.ts.map +1 -1
  82. package/build-types/custom-gradient-picker/stories/index.story.d.ts +1 -1
  83. package/build-types/custom-gradient-picker/stories/index.story.d.ts.map +1 -1
  84. package/build-types/custom-gradient-picker/test/index.d.ts +2 -0
  85. package/build-types/custom-gradient-picker/test/index.d.ts.map +1 -0
  86. package/build-types/custom-select-control/stories/index.story.d.ts +3 -3
  87. package/build-types/custom-select-control/stories/index.story.d.ts.map +1 -1
  88. package/build-types/custom-select-control-v2/stories/index.story.d.ts +3 -3
  89. package/build-types/date-time/stories/time.story.d.ts +1 -1
  90. package/build-types/date-time/stories/time.story.d.ts.map +1 -1
  91. package/build-types/date-time/time/index.d.ts.map +1 -1
  92. package/build-types/date-time/utils.d.ts +9 -0
  93. package/build-types/date-time/utils.d.ts.map +1 -1
  94. package/build-types/drop-zone/stories/index.story.d.ts +1 -1
  95. package/build-types/drop-zone/stories/index.story.d.ts.map +1 -1
  96. package/build-types/duotone-picker/stories/duotone-picker.story.d.ts +1 -1
  97. package/build-types/duotone-picker/stories/duotone-picker.story.d.ts.map +1 -1
  98. package/build-types/duotone-picker/stories/duotone-swatch.story.d.ts +3 -3
  99. package/build-types/duotone-picker/stories/duotone-swatch.story.d.ts.map +1 -1
  100. package/build-types/focal-point-picker/stories/index.story.d.ts +4 -4
  101. package/build-types/form-file-upload/stories/index.story.d.ts +5 -5
  102. package/build-types/form-file-upload/stories/index.story.d.ts.map +1 -1
  103. package/build-types/form-token-field/token-input.d.ts.map +1 -1
  104. package/build-types/guide/stories/index.story.d.ts +1 -1
  105. package/build-types/guide/stories/index.story.d.ts.map +1 -1
  106. package/build-types/icon/stories/index.story.d.ts +4 -4
  107. package/build-types/icon/stories/index.story.d.ts.map +1 -1
  108. package/build-types/input-control/stories/index.story.d.ts +7 -7
  109. package/build-types/input-control/stories/index.story.d.ts.map +1 -1
  110. package/build-types/keyboard-shortcuts/stories/index.story.d.ts +1 -1
  111. package/build-types/keyboard-shortcuts/stories/index.story.d.ts.map +1 -1
  112. package/build-types/menu-group/stories/index.story.d.ts +1 -1
  113. package/build-types/menu-group/stories/index.story.d.ts.map +1 -1
  114. package/build-types/menu-item/stories/index.story.d.ts +4 -4
  115. package/build-types/navigation/stories/index.story.d.ts +6 -6
  116. package/build-types/navigation/stories/index.story.d.ts.map +1 -1
  117. package/build-types/notice/stories/index.story.d.ts +5 -5
  118. package/build-types/notice/stories/index.story.d.ts.map +1 -1
  119. package/build-types/number-control/stories/index.story.d.ts +1 -1
  120. package/build-types/palette-edit/stories/index.story.d.ts +2 -2
  121. package/build-types/palette-edit/stories/index.story.d.ts.map +1 -1
  122. package/build-types/progress-bar/stories/index.story.d.ts +1 -1
  123. package/build-types/progress-bar/stories/index.story.d.ts.map +1 -1
  124. package/build-types/query-controls/stories/index.story.d.ts +1 -1
  125. package/build-types/query-controls/stories/index.story.d.ts.map +1 -1
  126. package/build-types/radio-control/index.d.ts.map +1 -1
  127. package/build-types/resizable-box/stories/index.story.d.ts +2 -2
  128. package/build-types/responsive-wrapper/stories/index.story.d.ts +1 -1
  129. package/build-types/responsive-wrapper/stories/index.story.d.ts.map +1 -1
  130. package/build-types/sandbox/stories/index.story.d.ts +1 -1
  131. package/build-types/sandbox/stories/index.story.d.ts.map +1 -1
  132. package/build-types/search-control/stories/index.story.d.ts +1 -1
  133. package/build-types/select-control/stories/index.story.d.ts +5 -5
  134. package/build-types/shortcut/stories/index.story.d.ts +1 -1
  135. package/build-types/shortcut/stories/index.story.d.ts.map +1 -1
  136. package/build-types/tab-panel/stories/index.story.d.ts +4 -4
  137. package/build-types/tab-panel/stories/index.story.d.ts.map +1 -1
  138. package/build-types/tabs/stories/index.story.d.ts +7 -7
  139. package/build-types/tabs/stories/index.story.d.ts.map +1 -1
  140. package/build-types/text/stories/index.story.d.ts +3 -3
  141. package/build-types/theme/stories/index.story.d.ts +1 -1
  142. package/build-types/toggle-control/stories/index.story.d.ts +2 -2
  143. package/build-types/toggle-group-control/stories/index.story.d.ts.map +1 -1
  144. package/build-types/toggle-group-control/toggle-group-control/as-button-group.d.ts.map +1 -1
  145. package/build-types/toggle-group-control/toggle-group-control/component.d.ts.map +1 -1
  146. package/build-types/toggle-group-control/toggle-group-control/styles.d.ts +0 -4
  147. package/build-types/toggle-group-control/toggle-group-control/styles.d.ts.map +1 -1
  148. package/build-types/toolbar/stories/index.story.d.ts +3 -3
  149. package/build-types/toolbar/stories/index.story.d.ts.map +1 -1
  150. package/build-types/tooltip/stories/index.story.d.ts +1 -1
  151. package/build-types/tooltip/stories/index.story.d.ts.map +1 -1
  152. package/build-types/tree-grid/stories/index.story.d.ts +1 -1
  153. package/build-types/tree-grid/stories/index.story.d.ts.map +1 -1
  154. package/build-types/tree-select/stories/index.story.d.ts +1 -1
  155. package/build-types/tree-select/stories/index.story.d.ts.map +1 -1
  156. package/build-types/v-stack/stories/index.story.d.ts +1 -1
  157. package/build-types/validated-form-controls/control-with-error.d.ts.map +1 -1
  158. package/build-types/validated-form-controls/test/checkbox-control.d.ts +2 -0
  159. package/build-types/validated-form-controls/test/checkbox-control.d.ts.map +1 -0
  160. package/build-types/validated-form-controls/test/combobox-control.d.ts +2 -0
  161. package/build-types/validated-form-controls/test/combobox-control.d.ts.map +1 -0
  162. package/build-types/validated-form-controls/test/custom-select-control.d.ts +2 -0
  163. package/build-types/validated-form-controls/test/custom-select-control.d.ts.map +1 -0
  164. package/build-types/validated-form-controls/test/form-token-field.d.ts +2 -0
  165. package/build-types/validated-form-controls/test/form-token-field.d.ts.map +1 -0
  166. package/build-types/validated-form-controls/test/input-control.d.ts +2 -0
  167. package/build-types/validated-form-controls/test/input-control.d.ts.map +1 -0
  168. package/build-types/validated-form-controls/test/number-control.d.ts +2 -0
  169. package/build-types/validated-form-controls/test/number-control.d.ts.map +1 -0
  170. package/build-types/validated-form-controls/test/radio-control.d.ts +2 -0
  171. package/build-types/validated-form-controls/test/radio-control.d.ts.map +1 -0
  172. package/build-types/validated-form-controls/test/range-control.d.ts +2 -0
  173. package/build-types/validated-form-controls/test/range-control.d.ts.map +1 -0
  174. package/build-types/validated-form-controls/test/select-control.d.ts +2 -0
  175. package/build-types/validated-form-controls/test/select-control.d.ts.map +1 -0
  176. package/build-types/validated-form-controls/test/text-control.d.ts +2 -0
  177. package/build-types/validated-form-controls/test/text-control.d.ts.map +1 -0
  178. package/build-types/validated-form-controls/test/textarea-control.d.ts +2 -0
  179. package/build-types/validated-form-controls/test/textarea-control.d.ts.map +1 -0
  180. package/build-types/validated-form-controls/test/toggle-control.d.ts +2 -0
  181. package/build-types/validated-form-controls/test/toggle-control.d.ts.map +1 -0
  182. package/build-types/validated-form-controls/test/toggle-group-control.d.ts +2 -0
  183. package/build-types/validated-form-controls/test/toggle-group-control.d.ts.map +1 -0
  184. package/build-types/validated-form-controls/validity-indicator.d.ts +2 -1
  185. package/build-types/validated-form-controls/validity-indicator.d.ts.map +1 -1
  186. package/package.json +24 -24
  187. package/src/button/style.scss +16 -5
  188. package/src/button-group/stories/index.story.tsx +1 -1
  189. package/src/combobox-control/index.tsx +6 -0
  190. package/src/combobox-control/stories/index.story.tsx +3 -2
  191. package/src/combobox-control/test/index.tsx +16 -9
  192. package/src/composite/legacy/stories/index.story.tsx +1 -1
  193. package/src/custom-gradient-picker/index.tsx +15 -4
  194. package/src/custom-gradient-picker/test/index.tsx +81 -0
  195. package/src/date-time/test/utils.test.ts +8 -11
  196. package/src/date-time/time/index.tsx +2 -12
  197. package/src/date-time/time/test/index.tsx +69 -0
  198. package/src/date-time/utils.ts +18 -0
  199. package/src/form-token-field/token-input.tsx +7 -1
  200. package/src/guide/style.scss +3 -0
  201. package/src/modal/style.scss +4 -7
  202. package/src/navigation/stories/index.story.tsx +1 -1
  203. package/src/radio-control/index.tsx +1 -0
  204. package/src/radio-control/test/index.tsx +5 -5
  205. package/src/radio-group/stories/index.story.tsx +1 -1
  206. package/src/snackbar/style.scss +1 -1
  207. package/src/toggle-group-control/stories/index.story.tsx +1 -0
  208. package/src/toggle-group-control/test/__snapshots__/index.tsx.snap +124 -164
  209. package/src/toggle-group-control/test/index.tsx +54 -0
  210. package/src/toggle-group-control/toggle-group-control/as-button-group.tsx +1 -0
  211. package/src/toggle-group-control/toggle-group-control/component.tsx +13 -8
  212. package/src/toggle-group-control/toggle-group-control/styles.ts +0 -6
  213. package/src/validated-form-controls/control-with-error.tsx +44 -4
  214. package/src/validated-form-controls/test/checkbox-control.tsx +49 -0
  215. package/src/validated-form-controls/test/combobox-control.tsx +61 -0
  216. package/src/validated-form-controls/test/control-with-error.tsx +182 -1
  217. package/src/validated-form-controls/test/custom-select-control.tsx +60 -0
  218. package/src/validated-form-controls/test/form-token-field.tsx +52 -0
  219. package/src/validated-form-controls/test/input-control.tsx +42 -0
  220. package/src/validated-form-controls/test/number-control.tsx +44 -0
  221. package/src/validated-form-controls/test/radio-control.tsx +61 -0
  222. package/src/validated-form-controls/test/range-control.tsx +73 -0
  223. package/src/validated-form-controls/test/select-control.tsx +57 -0
  224. package/src/validated-form-controls/test/text-control.tsx +49 -0
  225. package/src/validated-form-controls/test/textarea-control.tsx +51 -0
  226. package/src/validated-form-controls/test/toggle-control.tsx +49 -0
  227. package/src/validated-form-controls/test/toggle-group-control.tsx +28 -0
  228. package/src/validated-form-controls/validity-indicator.tsx +3 -0
@@ -0,0 +1,44 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { ValidatedNumberControl } from '../components';
4
+
5
+ describe( 'ValidatedNumberControl', () => {
6
+ it( 'should preserve the help description', () => {
7
+ render(
8
+ <ValidatedNumberControl label="Quantity" help="Enter a quantity." />
9
+ );
10
+
11
+ expect(
12
+ screen.getByRole( 'spinbutton', { name: 'Quantity' } )
13
+ ).toHaveAccessibleDescription( 'Enter a quantity.' );
14
+ } );
15
+
16
+ it( 'should append the validation error alongside the help description', async () => {
17
+ const user = userEvent.setup();
18
+ render(
19
+ <form>
20
+ <ValidatedNumberControl
21
+ label="Quantity"
22
+ help="Enter a quantity."
23
+ required
24
+ />
25
+ <button type="submit">Submit</button>
26
+ </form>
27
+ );
28
+
29
+ const input = screen.getByRole( 'spinbutton', {
30
+ name: /^Quantity/,
31
+ } );
32
+
33
+ await user.click( screen.getByRole( 'button', { name: 'Submit' } ) );
34
+
35
+ await waitFor( () => {
36
+ expect( input ).toHaveAccessibleDescription(
37
+ expect.stringContaining( 'Constraints not satisfied' )
38
+ );
39
+ } );
40
+ expect( input ).toHaveAccessibleDescription(
41
+ expect.stringContaining( 'Enter a quantity.' )
42
+ );
43
+ } );
44
+ } );
@@ -0,0 +1,61 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { ValidatedRadioControl } from '../components';
4
+
5
+ describe( 'ValidatedRadioControl', () => {
6
+ const options = [
7
+ { label: 'Small', value: 'small' },
8
+ { label: 'Medium', value: 'medium' },
9
+ { label: 'Large', value: 'large' },
10
+ ];
11
+
12
+ it( 'should preserve the help description on the radio group', () => {
13
+ render(
14
+ <ValidatedRadioControl
15
+ label="Size"
16
+ help="Choose a size."
17
+ options={ options }
18
+ onChange={ () => {} }
19
+ />
20
+ );
21
+
22
+ expect(
23
+ screen.getByRole( 'radiogroup', { name: 'Size' } )
24
+ ).toHaveAccessibleDescription( 'Choose a size.' );
25
+ } );
26
+
27
+ it( 'should append the validation error to the first radio input', async () => {
28
+ const user = userEvent.setup();
29
+ render(
30
+ <form>
31
+ <ValidatedRadioControl
32
+ label="Size"
33
+ help="Choose a size."
34
+ options={ options }
35
+ onChange={ () => {} }
36
+ required
37
+ />
38
+ <button type="submit">Submit</button>
39
+ </form>
40
+ );
41
+
42
+ await user.click( screen.getByRole( 'button', { name: 'Submit' } ) );
43
+
44
+ // The validation error targets the first radio input (the validity
45
+ // target), while the help description stays on the radiogroup.
46
+ const firstRadio = screen.getByRole( 'radio', {
47
+ name: 'Small',
48
+ } );
49
+ await waitFor( () => {
50
+ expect( firstRadio ).toHaveAccessibleDescription(
51
+ expect.stringContaining( 'Constraints not satisfied' )
52
+ );
53
+ } );
54
+
55
+ expect(
56
+ screen.getByRole( 'radiogroup', { name: /^Size/ } )
57
+ ).toHaveAccessibleDescription(
58
+ expect.stringContaining( 'Choose a size.' )
59
+ );
60
+ } );
61
+ } );
@@ -0,0 +1,73 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { useState, useRef } from '@wordpress/element';
4
+ import { ValidatedRangeControl } from '../components';
5
+
6
+ describe( 'ValidatedRangeControl', () => {
7
+ it( 'should preserve the help description', () => {
8
+ render(
9
+ <ValidatedRangeControl label="Opacity" help="Set the opacity." />
10
+ );
11
+
12
+ expect(
13
+ screen.getByRole( 'slider', { name: 'Opacity' } )
14
+ ).toHaveAccessibleDescription( 'Set the opacity.' );
15
+ } );
16
+
17
+ // Range inputs always have a value, so `required` never fails constraint
18
+ // validation. Use `customValidity` to trigger the validation error.
19
+ it( 'should append the validation error alongside the help description', async () => {
20
+ const user = userEvent.setup();
21
+
22
+ function TestComponent() {
23
+ const [ customValidity, setCustomValidity ] =
24
+ useState<
25
+ React.ComponentProps<
26
+ typeof ValidatedRangeControl
27
+ >[ 'customValidity' ]
28
+ >( undefined );
29
+ const ref = useRef< HTMLInputElement >( null );
30
+
31
+ return (
32
+ <>
33
+ <ValidatedRangeControl
34
+ ref={ ref }
35
+ label="Opacity"
36
+ help="Set the opacity."
37
+ customValidity={ customValidity }
38
+ />
39
+ <button
40
+ type="button"
41
+ onClick={ () => {
42
+ setCustomValidity( {
43
+ type: 'invalid',
44
+ message: 'Value out of range.',
45
+ } );
46
+ requestAnimationFrame(
47
+ () => ref.current?.reportValidity()
48
+ );
49
+ } }
50
+ >
51
+ Validate
52
+ </button>
53
+ </>
54
+ );
55
+ }
56
+
57
+ render( <TestComponent /> );
58
+
59
+ const slider = screen.getByRole( 'slider', { name: 'Opacity' } );
60
+ expect( slider ).toHaveAccessibleDescription( 'Set the opacity.' );
61
+
62
+ await user.click( screen.getByRole( 'button', { name: 'Validate' } ) );
63
+
64
+ await waitFor( () => {
65
+ expect( slider ).toHaveAccessibleDescription(
66
+ expect.stringContaining( 'Value out of range.' )
67
+ );
68
+ } );
69
+ expect( slider ).toHaveAccessibleDescription(
70
+ expect.stringContaining( 'Set the opacity.' )
71
+ );
72
+ } );
73
+ } );
@@ -0,0 +1,57 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { ValidatedSelectControl } from '../components';
4
+
5
+ describe( 'ValidatedSelectControl', () => {
6
+ const options = [
7
+ { label: 'Select a color...', value: '' },
8
+ { label: 'Red', value: 'red' },
9
+ { label: 'Blue', value: 'blue' },
10
+ ];
11
+
12
+ it( 'should preserve the help description', () => {
13
+ render(
14
+ <ValidatedSelectControl
15
+ label="Color"
16
+ help="Pick a color."
17
+ options={ options }
18
+ onChange={ () => {} }
19
+ />
20
+ );
21
+
22
+ expect(
23
+ screen.getByRole( 'combobox', { name: 'Color' } )
24
+ ).toHaveAccessibleDescription( 'Pick a color.' );
25
+ } );
26
+
27
+ it( 'should append the validation error alongside the help description', async () => {
28
+ const user = userEvent.setup();
29
+ render(
30
+ <form>
31
+ <ValidatedSelectControl
32
+ label="Color"
33
+ help="Pick a color."
34
+ options={ options }
35
+ onChange={ () => {} }
36
+ required
37
+ />
38
+ <button type="submit">Submit</button>
39
+ </form>
40
+ );
41
+
42
+ const select = screen.getByRole( 'combobox', {
43
+ name: /^Color/,
44
+ } );
45
+
46
+ await user.click( screen.getByRole( 'button', { name: 'Submit' } ) );
47
+
48
+ await waitFor( () => {
49
+ expect( select ).toHaveAccessibleDescription(
50
+ expect.stringContaining( 'Constraints not satisfied' )
51
+ );
52
+ } );
53
+ expect( select ).toHaveAccessibleDescription(
54
+ expect.stringContaining( 'Pick a color.' )
55
+ );
56
+ } );
57
+ } );
@@ -0,0 +1,49 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { ValidatedTextControl } from '../components';
4
+
5
+ describe( 'ValidatedTextControl', () => {
6
+ it( 'should preserve the help description', () => {
7
+ render(
8
+ <ValidatedTextControl
9
+ label="Name"
10
+ help="Enter your full name."
11
+ onChange={ () => {} }
12
+ value=""
13
+ />
14
+ );
15
+
16
+ expect(
17
+ screen.getByRole( 'textbox', { name: 'Name' } )
18
+ ).toHaveAccessibleDescription( 'Enter your full name.' );
19
+ } );
20
+
21
+ it( 'should append the validation error alongside the help description', async () => {
22
+ const user = userEvent.setup();
23
+ render(
24
+ <form>
25
+ <ValidatedTextControl
26
+ label="Name"
27
+ help="Enter your full name."
28
+ onChange={ () => {} }
29
+ value=""
30
+ required
31
+ />
32
+ <button type="submit">Submit</button>
33
+ </form>
34
+ );
35
+
36
+ const input = screen.getByRole( 'textbox', { name: /^Name/ } );
37
+
38
+ await user.click( screen.getByRole( 'button', { name: 'Submit' } ) );
39
+
40
+ await waitFor( () => {
41
+ expect( input ).toHaveAccessibleDescription(
42
+ expect.stringContaining( 'Constraints not satisfied' )
43
+ );
44
+ } );
45
+ expect( input ).toHaveAccessibleDescription(
46
+ expect.stringContaining( 'Enter your full name.' )
47
+ );
48
+ } );
49
+ } );
@@ -0,0 +1,51 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { ValidatedTextareaControl } from '../components';
4
+
5
+ describe( 'ValidatedTextareaControl', () => {
6
+ it( 'should preserve the help description', () => {
7
+ render(
8
+ <ValidatedTextareaControl
9
+ label="Bio"
10
+ help="A short bio."
11
+ onChange={ () => {} }
12
+ value=""
13
+ />
14
+ );
15
+
16
+ expect(
17
+ screen.getByRole( 'textbox', { name: 'Bio' } )
18
+ ).toHaveAccessibleDescription( 'A short bio.' );
19
+ } );
20
+
21
+ it( 'should append the validation error alongside the help description', async () => {
22
+ const user = userEvent.setup();
23
+ render(
24
+ <form>
25
+ <ValidatedTextareaControl
26
+ label="Bio"
27
+ help="A short bio."
28
+ onChange={ () => {} }
29
+ value=""
30
+ required
31
+ />
32
+ <button type="submit">Submit</button>
33
+ </form>
34
+ );
35
+
36
+ const textarea = screen.getByRole( 'textbox', {
37
+ name: /^Bio/,
38
+ } );
39
+
40
+ await user.click( screen.getByRole( 'button', { name: 'Submit' } ) );
41
+
42
+ await waitFor( () => {
43
+ expect( textarea ).toHaveAccessibleDescription(
44
+ expect.stringContaining( 'Constraints not satisfied' )
45
+ );
46
+ } );
47
+ expect( textarea ).toHaveAccessibleDescription(
48
+ expect.stringContaining( 'A short bio.' )
49
+ );
50
+ } );
51
+ } );
@@ -0,0 +1,49 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { ValidatedToggleControl } from '../components';
4
+
5
+ describe( 'ValidatedToggleControl', () => {
6
+ it( 'should preserve the help description', () => {
7
+ render(
8
+ <ValidatedToggleControl
9
+ label="Dark mode"
10
+ help="Enable dark mode."
11
+ onChange={ () => {} }
12
+ />
13
+ );
14
+
15
+ expect(
16
+ screen.getByRole( 'checkbox', { name: 'Dark mode' } )
17
+ ).toHaveAccessibleDescription( 'Enable dark mode.' );
18
+ } );
19
+
20
+ it( 'should append the validation error alongside the help description', async () => {
21
+ const user = userEvent.setup();
22
+ render(
23
+ <form>
24
+ <ValidatedToggleControl
25
+ label="Dark mode"
26
+ help="Enable dark mode."
27
+ onChange={ () => {} }
28
+ required
29
+ />
30
+ <button type="submit">Submit</button>
31
+ </form>
32
+ );
33
+
34
+ const toggle = screen.getByRole( 'checkbox', {
35
+ name: /^Dark mode/,
36
+ } );
37
+
38
+ await user.click( screen.getByRole( 'button', { name: 'Submit' } ) );
39
+
40
+ await waitFor( () => {
41
+ expect( toggle ).toHaveAccessibleDescription(
42
+ expect.stringContaining( 'Constraints not satisfied' )
43
+ );
44
+ } );
45
+ expect( toggle ).toHaveAccessibleDescription(
46
+ expect.stringContaining( 'Enable dark mode.' )
47
+ );
48
+ } );
49
+ } );
@@ -0,0 +1,28 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { ValidatedToggleGroupControl } from '../components';
3
+ import { ToggleGroupControlOption } from '../../toggle-group-control';
4
+
5
+ // The `help` prop is rendered visually by BaseControl but is not
6
+ // programmatically associated with the toggle group via aria-describedby.
7
+ // Additionally, the validity target is a hidden delegate radio input, not the
8
+ // toggle group itself. These are pre-existing bugs, not caused by ControlWithError.
9
+ describe( 'ValidatedToggleGroupControl', () => {
10
+ // eslint-disable-next-line jest/no-disabled-tests
11
+ it.skip( 'should preserve the help description', () => {
12
+ render(
13
+ <ValidatedToggleGroupControl
14
+ label="Alignment"
15
+ help="Choose text alignment."
16
+ value="left"
17
+ onChange={ () => {} }
18
+ >
19
+ <ToggleGroupControlOption label="Left" value="left" />
20
+ <ToggleGroupControlOption label="Center" value="center" />
21
+ </ValidatedToggleGroupControl>
22
+ );
23
+
24
+ expect(
25
+ screen.getByRole( 'radiogroup', { name: 'Alignment' } )
26
+ ).toHaveAccessibleDescription( 'Choose text alignment.' );
27
+ } );
28
+ } );
@@ -15,9 +15,11 @@ import Icon from '../icon';
15
15
  import Spinner from '../spinner';
16
16
 
17
17
  export function ValidityIndicator( {
18
+ id,
18
19
  type,
19
20
  message,
20
21
  }: {
22
+ id?: string;
21
23
  type: 'validating' | 'valid' | 'invalid';
22
24
  message?: string;
23
25
  } ) {
@@ -27,6 +29,7 @@ export function ValidityIndicator( {
27
29
  };
28
30
  return (
29
31
  <p
32
+ id={ id }
30
33
  className={ clsx(
31
34
  'components-validated-control__indicator',
32
35
  `is-${ type }`