@cnamts/synapse 1.0.23 → 1.0.25

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 (574) hide show
  1. package/README.md +27 -5
  2. package/dist/AutocompleteFilter-D7qBuCAP.js +114 -0
  3. package/dist/AutocompleteFilter-Df9i5mAl.cjs +1 -0
  4. package/dist/DateFilter-BJD6FMev.cjs +1 -0
  5. package/dist/{DateFilter-Dc-gSGwk.js → DateFilter-BitMWrMU.js} +1 -1
  6. package/dist/{NumberFilter-vP38Wp6j.js → NumberFilter-BTLUxw0a.js} +3 -3
  7. package/dist/NumberFilter-DGCzCXzI.cjs +1 -0
  8. package/dist/{PeriodFilter-Ba1uYUnT.js → PeriodFilter-B5rUIPAC.js} +1 -1
  9. package/dist/PeriodFilter-DO_ecTZW.cjs +1 -0
  10. package/dist/SelectFilter-CGwcKWLm.cjs +1 -0
  11. package/dist/SelectFilter-l4QnRcuk.js +135 -0
  12. package/dist/TextFilter-B8nf7xoK.cjs +1 -0
  13. package/dist/{TextFilter-B84dpnoq.js → TextFilter-C9hj6Qrp.js} +7 -7
  14. package/dist/apLightTheme-CEK4iY3f.cjs +1 -0
  15. package/dist/apLightTheme-DnIM24Lv.js +950 -0
  16. package/dist/components/Amelipro/AmeliproAutoCompleteField/AmeliproAutoCompleteField.d.ts +7022 -9616
  17. package/dist/components/Amelipro/AmeliproCarousel/AmeliproCarousel.d.ts +2 -2
  18. package/dist/components/Amelipro/AmeliproIconBtn/AmeliproIconBtn.d.ts +2 -2
  19. package/dist/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressCityRow/AmeliproPostalAddressCityRow.d.ts +40 -40
  20. package/dist/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressField.d.ts +60 -60
  21. package/dist/components/Amelipro/AmeliproSelect/AmeliproSelect.d.ts +7168 -9762
  22. package/dist/components/Amelipro/AmeliproStepper/AmeliproStepper.d.ts +2 -2
  23. package/dist/components/Amelipro/AmeliproTabs/AmeliproTabs.d.ts +7501 -10095
  24. package/dist/components/Amelipro/AmeliproTextArea/AmeliproTextArea.d.ts +21 -21
  25. package/dist/components/Amelipro/AmeliproTextField/AmeliproTextField.d.ts +41 -41
  26. package/dist/components/Amelipro/StructureMenu/StructureTabs/StructureTabs.d.ts +2 -2
  27. package/dist/components/CookiesSelection/CookiesInformation/CookiesInformation.d.ts +20 -498
  28. package/dist/components/Customs/Selects/SyAutocomplete/SyAutocomplete.d.ts +117 -151
  29. package/dist/components/Customs/Selects/SyInputSelect/SyInputSelect.d.ts +5 -5
  30. package/dist/components/Customs/Selects/SySelect/SySelect.d.ts +12 -16
  31. package/dist/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.d.ts +8 -8
  32. package/dist/components/Customs/SyCheckbox/SyCheckbox.d.ts +31 -506
  33. package/dist/components/Customs/SyRadioGroup/SyRadioGroup.d.ts +28 -506
  34. package/dist/components/Customs/SyTabs/SyTabs.d.ts +13 -11
  35. package/dist/components/Customs/SyTextField/SyTextField.d.ts +69 -89
  36. package/dist/components/DatePicker/CalendarMode/DatePicker.d.ts +627 -791
  37. package/dist/components/DatePicker/ComplexDatePicker/ComplexDatePicker.d.ts +315 -412
  38. package/dist/components/DatePicker/DateTextInput/DateTextInput.d.ts +112 -160
  39. package/dist/components/DatePicker/composables/index.d.ts +1 -0
  40. package/dist/components/DatePicker/composables/useDatePickerFocusTrap.d.ts +11 -0
  41. package/dist/components/DatePicker/composables/useDatePickerState.d.ts +1 -0
  42. package/dist/components/DatePicker/composables/useDateTextField.d.ts +4 -4
  43. package/dist/components/DatePicker/composables/useDateValidation.d.ts +3 -3
  44. package/dist/components/DatePicker/composables/useInputBlurHandler.d.ts +2 -2
  45. package/dist/components/DatePicker/composables/useManualDateValidation.d.ts +2 -2
  46. package/dist/components/FileList/FileList.d.ts +6 -0
  47. package/dist/components/FilterSideBar/FilterSideBar.d.ts +2 -0
  48. package/dist/components/HeaderNavigationBar/HeaderNavigationBar.d.ts +17 -17
  49. package/dist/components/HeaderNavigationBar/HorizontalNavbar/HorizontalNavbar.d.ts +3 -3
  50. package/dist/components/HeaderToolbar/HeaderToolbar.d.ts +20 -28
  51. package/dist/components/LunarCalendar/useLunarCalendarValidation.d.ts +3 -3
  52. package/dist/components/MonthPicker/MonthPicker.d.ts +86 -127
  53. package/dist/components/MonthPicker/MonthPickerText/MonthPickerInput.d.ts +85 -126
  54. package/dist/components/NirField/NirField.d.ts +202 -278
  55. package/dist/components/NirField/locales.d.ts +10 -10
  56. package/dist/components/NirField/useNirValidation.d.ts +64 -0
  57. package/dist/components/PasswordField/PasswordField.d.ts +8 -9
  58. package/dist/components/PeriodField/PeriodField.d.ts +1352 -1680
  59. package/dist/components/PhoneField/PhoneField.d.ts +88 -129
  60. package/dist/components/RangeField/RangeSlider/RangeSlider.d.ts +12 -12
  61. package/dist/components/SyTextArea/SyTextArea.d.ts +34 -14
  62. package/dist/components/SyTextArea/useDefaultValidationRules.d.ts +11 -0
  63. package/dist/components/Tables/SyServerTable/SyServerTable.d.ts +10 -6
  64. package/dist/components/Tables/SyTable/SyTable.d.ts +10 -6
  65. package/dist/components/Tables/common/SyTableFilter.d.ts +2 -3
  66. package/dist/components/Tables/common/SyTablePagination.d.ts +19 -19
  67. package/dist/components/Tables/common/TableHeader.d.ts +5 -0
  68. package/dist/components/Tables/common/filters/AutocompleteFilter.d.ts +120 -0
  69. package/dist/components/Tables/common/filters/locales.d.ts +0 -1
  70. package/dist/components/Tables/common/locales.d.ts +3 -0
  71. package/dist/components/Tables/common/types.d.ts +21 -3
  72. package/dist/components/Tables/common/useClickableTableRow.d.ts +17 -0
  73. package/dist/components/Tables/common/usePagination.d.ts +3 -1
  74. package/dist/components/Tables/common/usePinnedColumns.d.ts +31 -0
  75. package/dist/components/Tables/common/useTableHeaders.d.ts +2 -0
  76. package/dist/components/Tables/common/useTableRowCheckboxAccessibility.d.ts +5 -0
  77. package/dist/components/UploadWorkflow/UploadWorkflow.d.ts +7 -6
  78. package/dist/composables/date/useDatePickerAccessibility.d.ts +1 -1
  79. package/dist/composables/rules/useFieldValidation.d.ts +4 -4
  80. package/dist/composables/unifyValidation/documentationValidationProps.d.ts +230 -0
  81. package/dist/composables/unifyValidation/useCustomValidation.d.ts +8 -0
  82. package/dist/composables/unifyValidation/useValidation.d.ts +102 -0
  83. package/dist/composables/unifyValidation/useVuetifyValidation.d.ts +18 -0
  84. package/dist/composables/useFormFieldErrorHandling.d.ts +2 -2
  85. package/dist/composables/validation/useFormValidation.d.ts +11 -2
  86. package/dist/composables/validation/useValidation.d.ts +19 -9
  87. package/dist/design-system-v3.d.ts +2 -0
  88. package/dist/design-system-v3.js +186 -187
  89. package/dist/design-system-v3.umd.cjs +1 -1066
  90. package/dist/designTokens/tokens/amelipro/apColors.d.ts +10 -10
  91. package/dist/designTokens/tokens/amelipro/apColors2026.d.ts +1 -2
  92. package/dist/designTokens/tokens/amelipro/apContextual.d.ts +44 -0
  93. package/dist/designTokens/tokens/amelipro/apSemantic.d.ts +1 -1
  94. package/dist/designTokens/tokens/baseColors.d.ts +127 -0
  95. package/dist/designTokens/tokens/baseContextualTokens.d.ts +50 -0
  96. package/dist/designTokens/tokens/cnam/cnamColors.d.ts +10 -10
  97. package/dist/designTokens/tokens/cnam/cnamSemantic.d.ts +1 -1
  98. package/dist/designTokens/tokens/pa/paColors.d.ts +1 -166
  99. package/dist/designTokens/tokens/pa/paSemantic.d.ts +1 -1
  100. package/dist/designTokens/utils/buildColorClassMap.d.ts +12 -0
  101. package/dist/designTokens/utils/createFlattenTheme.d.ts +1 -3
  102. package/dist/designTokens/utils/index.d.ts +2 -2
  103. package/dist/main-ByDPHpae.cjs +1067 -0
  104. package/dist/main-Cpx8Co6H.js +38869 -0
  105. package/dist/main.d.ts +0 -1
  106. package/dist/synapse.css +1 -0
  107. package/dist/tooth-11-D3sLWv2n.cjs +1 -0
  108. package/dist/tooth-12-CXrLuH03.cjs +1 -0
  109. package/dist/tooth-13-BSfo5fpT.cjs +1 -0
  110. package/dist/tooth-14-DMzulx0h.cjs +1 -0
  111. package/dist/tooth-15-BKRFVi-9.cjs +1 -0
  112. package/dist/tooth-16-CpuxAbuM.cjs +1 -0
  113. package/dist/tooth-17-BPoahUdg.cjs +1 -0
  114. package/dist/tooth-18-DhHJz8sy.cjs +1 -0
  115. package/dist/tooth-21-Dgd5hn_X.cjs +1 -0
  116. package/dist/tooth-22-C2Tn19sB.cjs +1 -0
  117. package/dist/tooth-23-C9uaaSGb.cjs +1 -0
  118. package/dist/tooth-24-BrK9UGpf.cjs +1 -0
  119. package/dist/tooth-25-CE_EfGNp.cjs +1 -0
  120. package/dist/tooth-26-Ctv4i9Fy.cjs +1 -0
  121. package/dist/tooth-27-C5J7JkWM.cjs +1 -0
  122. package/dist/tooth-28-Z9oWqjo0.cjs +1 -0
  123. package/dist/tooth-31-BrYqmkTi.cjs +1 -0
  124. package/dist/tooth-32-BNNR0oCZ.cjs +1 -0
  125. package/dist/tooth-33-DuxvqO2J.cjs +1 -0
  126. package/dist/tooth-34-BCSCXMB6.cjs +1 -0
  127. package/dist/tooth-35-BLUXkX88.cjs +1 -0
  128. package/dist/tooth-36-IrKHYqlA.cjs +1 -0
  129. package/dist/tooth-37-BYqpdMwo.cjs +1 -0
  130. package/dist/tooth-38-B_eNXXdu.cjs +1 -0
  131. package/dist/tooth-41-Ddva4Ot8.cjs +1 -0
  132. package/dist/tooth-42-szcDqlM0.cjs +1 -0
  133. package/dist/tooth-43-B3ka6rQm.cjs +1 -0
  134. package/dist/tooth-44-CazyQucj.cjs +1 -0
  135. package/dist/tooth-45-B4HQtc8n.cjs +1 -0
  136. package/dist/tooth-46-BPM40gbG.cjs +1 -0
  137. package/dist/tooth-47-Dvr20dlh.cjs +1 -0
  138. package/dist/tooth-48-Bd8ljGsF.cjs +1 -0
  139. package/dist/tooth-51-OBpwCOF3.cjs +1 -0
  140. package/dist/tooth-52-aKxyHcmq.cjs +1 -0
  141. package/dist/tooth-53-vCwJjTOc.cjs +1 -0
  142. package/dist/tooth-54-DsWu2iFy.cjs +1 -0
  143. package/dist/tooth-55-BxC1X2Dn.cjs +1 -0
  144. package/dist/tooth-61-BbLvxMQi.cjs +1 -0
  145. package/dist/tooth-62-CmTkWczP.cjs +1 -0
  146. package/dist/tooth-63-DI7l_2qI.cjs +1 -0
  147. package/dist/tooth-64-B21sOsJh.cjs +1 -0
  148. package/dist/tooth-65-D2ZC2VEr.cjs +1 -0
  149. package/dist/tooth-71-D473PPO5.cjs +1 -0
  150. package/dist/tooth-72-Drh1wnNu.cjs +1 -0
  151. package/dist/tooth-73-DzlwYI23.cjs +1 -0
  152. package/dist/tooth-74-8aGvcZPg.cjs +1 -0
  153. package/dist/tooth-75-BFK7At_r.cjs +1 -0
  154. package/dist/tooth-81-BZmR-I0M.cjs +1 -0
  155. package/dist/tooth-82-euVfUUZV.cjs +1 -0
  156. package/dist/tooth-83-KV010j64.cjs +1 -0
  157. package/dist/tooth-84-BBg1RjhZ.cjs +1 -0
  158. package/dist/tooth-85-Cr-kc1wM.cjs +1 -0
  159. package/dist/utils/functions/classToHex.d.ts +1 -1
  160. package/dist/utils/functions/createHexResolver.d.ts +16 -0
  161. package/dist/vuetifyConfig.js +522 -0
  162. package/dist/vuetifyConfig.umd.cjs +1 -0
  163. package/package.json +37 -20
  164. package/src/assets/amelipro/apTokens2026.scss +5 -5
  165. package/src/assets/overrides/_breakpoints.scss +25 -0
  166. package/src/assets/overrides/_btns.scss +0 -8
  167. package/src/assets/overrides/_forms.scss +1 -3
  168. package/src/assets/overrides/_icons.scss +14 -10
  169. package/src/assets/overrides/_otp.scss +41 -0
  170. package/src/assets/overrides/_tables.scss +11 -20
  171. package/src/assets/overrides/_tooltips.scss +17 -7
  172. package/src/assets/overrides/_typography.scss +35 -47
  173. package/src/assets/overrides/_utilities.scss +43 -47
  174. package/src/assets/themes.scss +1 -0
  175. package/src/components/Amelipro/AmeliproAccordion/AmeliproAccordionTemplate/AmeliproAccordionTemplate.vue +20 -20
  176. package/src/components/Amelipro/AmeliproAccordionFrieze/AmeliproAccordionFrieze.vue +12 -14
  177. package/src/components/Amelipro/AmeliproAccordionList/AmeliproAccordionList.vue +4 -6
  178. package/src/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResultTemplate/AmeliproAccordionResultTemplate.vue +5 -5
  179. package/src/components/Amelipro/AmeliproAccordionResultList/AmeliproAccordionResultList.vue +4 -6
  180. package/src/components/Amelipro/AmeliproAutoCompleteField/AmeliproAutoCompleteField.vue +4 -6
  181. package/src/components/Amelipro/AmeliproAutoCompleteField/__tests__/__snapshots__/AmeliproAutoCompleteField.spec.ts.snap +2 -2
  182. package/src/components/Amelipro/AmeliproBadge/AmeliproBadge.vue +4 -6
  183. package/src/components/Amelipro/AmeliproBreadcrumb/AmeliproBreadcrumb.vue +1 -3
  184. package/src/components/Amelipro/AmeliproBtn/AmeliproBtn.vue +4 -6
  185. package/src/components/Amelipro/AmeliproCallback/AmeliproCallback.vue +2 -2
  186. package/src/components/Amelipro/AmeliproCard/AmeliproCard.vue +31 -31
  187. package/src/components/Amelipro/AmeliproCarousel/AmeliproCarousel.vue +5 -7
  188. package/src/components/Amelipro/AmeliproCheckbox/AmeliproCheckbox.vue +13 -15
  189. package/src/components/Amelipro/AmeliproCheckboxGroup/AmeliproCheckboxGroup.vue +23 -23
  190. package/src/components/Amelipro/AmeliproChips/AmeliproChips.vue +1 -3
  191. package/src/components/Amelipro/AmeliproClickableTile/AmeliproClickableTile.vue +17 -12
  192. package/src/components/Amelipro/AmeliproClickableTile/tests/__snapshots__/AmeliproClickableTile.spec.ts.snap +2 -2
  193. package/src/components/Amelipro/AmeliproCopyBtn/AmeliproCopyBtn.vue +4 -6
  194. package/src/components/Amelipro/AmeliproCustomSelector/AmeliproCustomSelector.vue +13 -13
  195. package/src/components/Amelipro/AmeliproDentalChart/AmeliproDentalChart.vue +4 -2
  196. package/src/components/Amelipro/AmeliproDentalChart/AmeliproTooth/AmeliproTooth.vue +4 -4
  197. package/src/components/Amelipro/AmeliproDialog/AmeliproDialog.vue +5 -7
  198. package/src/components/Amelipro/AmeliproDisclosure/AmeliproDisclosure.vue +1 -3
  199. package/src/components/Amelipro/AmeliproErrorTemplate/AmeliproErrorTemplate.vue +2 -4
  200. package/src/components/Amelipro/AmeliproFilePreview/AmeliproFilePreview.vue +4 -6
  201. package/src/components/Amelipro/AmeliproFilters/AmeliproFilters.vue +13 -13
  202. package/src/components/Amelipro/AmeliproFirstLogin/AmeliproFirstLogin.vue +3 -5
  203. package/src/components/Amelipro/AmeliproFooter/AmeliproFooter.vue +1 -3
  204. package/src/components/Amelipro/AmeliproHeader/AmeliproHeader.vue +2 -4
  205. package/src/components/Amelipro/AmeliproHeader/AmeliproHeaderBar/AmeliproHeaderBar.vue +1 -3
  206. package/src/components/Amelipro/AmeliproHeader/AmeliproHeaderBar/AmeliproHeaderBrandSection/AmeliproHeaderBrandSection.vue +20 -16
  207. package/src/components/Amelipro/AmeliproHeader/AmeliproHeaderBar/AmeliproHeaderBrandSection/__tests__/__snapshots__/AmeliproHeaderBrandSection.spec.ts.snap +2 -2
  208. package/src/components/Amelipro/AmeliproIconBtn/AmeliproIconBtn.vue +2 -4
  209. package/src/components/Amelipro/AmeliproIllustratedDataTile/AmeliproIllustratedDataTile.vue +5 -7
  210. package/src/components/Amelipro/AmeliproIllustratedRadioGroup/AmeliproIllustratedRadioGroup.vue +6 -8
  211. package/src/components/Amelipro/AmeliproMailTile/AmeliproMailTile.vue +14 -14
  212. package/src/components/Amelipro/AmeliproMenu/AmeliproMenu.vue +11 -13
  213. package/src/components/Amelipro/AmeliproMessagingLayout/AmeliproDropdownMenu/AmeliproDropdownMenu.vue +2 -4
  214. package/src/components/Amelipro/AmeliproMessagingLayout/AmeliproMessagingLayout.vue +8 -8
  215. package/src/components/Amelipro/AmeliproMultipleFoldingCard/AmeliproMultipleFoldingCard.vue +40 -40
  216. package/src/components/Amelipro/AmeliproNumberedCard/AmeliproNumberedCard.vue +27 -27
  217. package/src/components/Amelipro/AmeliproOnboarding/AmeliproOnboarding.vue +3 -5
  218. package/src/components/Amelipro/AmeliproPageLayout/AmeliproPageLayout.vue +2 -4
  219. package/src/components/Amelipro/AmeliproPagination/AmeliproPagination.vue +2 -4
  220. package/src/components/Amelipro/AmeliproPagination/AmeliproPaginationBtn/AmeliproPaginationBtn.vue +3 -5
  221. package/src/components/Amelipro/AmeliproPatientBanner/AmeliproPatientBanner.vue +1 -3
  222. package/src/components/Amelipro/AmeliproPatientLogged/AmeliproPatientLogged.vue +2 -4
  223. package/src/components/Amelipro/AmeliproPatientLogin/AmeliproPatientLogin.vue +4 -4
  224. package/src/components/Amelipro/AmeliproPatientLogin/AmeliproPatientLoginForm/AmeliproPatientLoginForm.vue +1 -3
  225. package/src/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressCityRow/AmeliproPostalAddressCityRow.vue +3 -5
  226. package/src/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressField.vue +3 -5
  227. package/src/components/Amelipro/AmeliproRadioGroup/AmeliproRadioGroup.vue +23 -23
  228. package/src/components/Amelipro/AmeliproResultList/AmeliproResultList.vue +4 -6
  229. package/src/components/Amelipro/AmeliproSelect/AmeliproSelect.vue +12 -22
  230. package/src/components/Amelipro/AmeliproStateTile/AmeliproStateTile.vue +10 -12
  231. package/src/components/Amelipro/AmeliproStepper/AmeliproStepper.vue +17 -17
  232. package/src/components/Amelipro/AmeliproTable/AmeliproTable.vue +8 -10
  233. package/src/components/Amelipro/AmeliproTabs/AmeliproTabBtn/AmeliproTabBtn.vue +6 -8
  234. package/src/components/Amelipro/AmeliproTabs/AmeliproTabs.vue +4 -4
  235. package/src/components/Amelipro/AmeliproTextArea/AmeliproTextArea.vue +4 -6
  236. package/src/components/Amelipro/AmeliproTextArea/__tests__/__snapshots__/AmeliproTextArea.spec.ts.snap +2 -2
  237. package/src/components/Amelipro/AmeliproTextField/AmeliproTextField.vue +7 -9
  238. package/src/components/Amelipro/AmeliproTileBtn/AmeliproTileBtn.vue +3 -5
  239. package/src/components/Amelipro/AmeliproTooltips/AmeliproTooltips.vue +0 -2
  240. package/src/components/Amelipro/AmeliproUpload/AmeliproUpload.vue +6 -6
  241. package/src/components/Amelipro/ServiceMenu/ServiceMenu.vue +2 -2
  242. package/src/components/Amelipro/StructureMenu/StructureBtn/StructureBtn.vue +4 -4
  243. package/src/components/Amelipro/StructureMenu/StructureItem/StructureItem.vue +4 -6
  244. package/src/components/Amelipro/StructureMenu/StructureMenu.vue +2 -2
  245. package/src/components/Amelipro/StructureMenu/StructureTabs/StructureTabs.vue +2 -2
  246. package/src/components/Amelipro/UserMenu/UserMenu.vue +1 -3
  247. package/src/components/BackBtn/tests/BackBtn.visual.cy.ts +43 -0
  248. package/src/components/BackBtn/tests/__snapshots__/back-btn-custom-bg.snap.png +0 -0
  249. package/src/components/BackBtn/tests/__snapshots__/back-btn-dark-mode.snap.png +0 -0
  250. package/src/components/BackBtn/tests/__snapshots__/back-btn-default.snap.png +0 -0
  251. package/src/components/BackBtn/tests/__snapshots__/back-btn-no-icon.snap.png +0 -0
  252. package/src/components/Captcha/Captcha.vue +1 -3
  253. package/src/components/Captcha/accessibilite/Accessibility.mdx +86 -8
  254. package/src/components/Captcha/tests/__snapshots__/Captcha.spec.ts.snap +12 -12
  255. package/src/components/ChipList/ChipList.stories.ts +0 -15
  256. package/src/components/ChipList/ChipList.vue +19 -17
  257. package/src/components/ChipList/accessibilite/Accessibility.mdx +83 -10
  258. package/src/components/ChipList/tests/ChipList.a11y.spec.ts +41 -0
  259. package/src/components/CookiesSelection/CookiesInformation/CookiesInformation.vue +0 -2
  260. package/src/components/CopyBtn/CopyBtn.vue +1 -3
  261. package/src/components/Customs/Selects/SelectBtnField/SelectBtnField.vue +17 -17
  262. package/src/components/Customs/Selects/SelectBtnField/accessibilite/Accessibility.mdx +0 -9
  263. package/src/components/Customs/Selects/SelectBtnField/tests/SelectBtnField.spec.ts +31 -0
  264. package/src/components/Customs/Selects/SyAutocomplete/SyAutocomplete.stories.ts +66 -0
  265. package/src/components/Customs/Selects/SyAutocomplete/SyAutocomplete.vue +28 -7
  266. package/src/components/Customs/Selects/SyAutocomplete/tests/SyAutocomplete.a11y.spec.ts +18 -0
  267. package/src/components/Customs/Selects/SyAutocomplete/tests/SyAutocomplete.spec.ts +209 -0
  268. package/src/components/Customs/Selects/SyAutocomplete/utils/ariaManager.ts +14 -10
  269. package/src/components/Customs/Selects/SyInputSelect/SyInputSelect.stories.ts +4 -4
  270. package/src/components/Customs/Selects/SyInputSelect/SyInputSelect.vue +12 -15
  271. package/src/components/Customs/Selects/SyInputSelect/tests/SyInputSelect.spec.ts +10 -10
  272. package/src/components/Customs/Selects/SySelect/SySelect.stories.ts +1 -39
  273. package/src/components/Customs/Selects/SySelect/SySelect.vue +274 -71
  274. package/src/components/Customs/Selects/SySelect/accessibilite/Accessibility.mdx +199 -269
  275. package/src/components/Customs/Selects/SySelect/tests/SySelect.spec.ts +89 -0
  276. package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.vue +6 -9
  277. package/src/components/Customs/SyCheckbox/SyCheckbox.mdx +5 -0
  278. package/src/components/Customs/SyCheckbox/SyCheckbox.stories.ts +10 -16
  279. package/src/components/Customs/SyCheckbox/SyCheckbox.vue +45 -11
  280. package/src/components/Customs/SyCheckbox/accessibilite/Accessibility.mdx +53 -3
  281. package/src/components/Customs/SyCheckbox/tests/SyCheckbox.a11y.spec.ts +134 -2
  282. package/src/components/Customs/SyForm/SyForm.stories.ts +31 -5
  283. package/src/components/Customs/SyPagination/SyPagination.vue +0 -2
  284. package/src/components/Customs/SyRadioGroup/SyRadioGroup.vue +4 -7
  285. package/src/components/Customs/SyTabs/SyTabs.mdx +0 -58
  286. package/src/components/Customs/SyTabs/SyTabs.stories.ts +32 -33
  287. package/src/components/Customs/SyTabs/SyTabs.vue +87 -67
  288. package/src/components/Customs/SyTabs/accessibilite/Accessibility.mdx +83 -23
  289. package/src/components/Customs/SyTabs/tests/SyTabs.a11y.spec.ts +88 -0
  290. package/src/components/Customs/SyTabs/tests/SyTabs.spec.ts +46 -1
  291. package/src/components/Customs/SyTextField/SyTextField.mdx +1 -1
  292. package/src/components/Customs/SyTextField/SyTextField.stories.ts +50 -68
  293. package/src/components/Customs/SyTextField/SyTextField.vue +156 -161
  294. package/src/components/Customs/SyTextField/tests/SyTextField.a11y.spec.ts +32 -0
  295. package/src/components/Customs/SyTextField/tests/SyTextField.spec.ts +120 -11
  296. package/src/components/DatePicker/CalendarMode/DatePicker.stories.ts +62 -58
  297. package/src/components/DatePicker/CalendarMode/DatePicker.vue +342 -237
  298. package/src/components/DatePicker/CalendarMode/accessibilite/Accessibility.mdx +82 -0
  299. package/src/components/DatePicker/CalendarMode/tests/DatePicker.a11y.spec.ts +141 -0
  300. package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.stories.ts +2 -56
  301. package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.vue +215 -175
  302. package/src/components/DatePicker/ComplexDatePicker/accessibilite/Accessibility.mdx +76 -0
  303. package/src/components/DatePicker/ComplexDatePicker/tests/ComplexDatePicker.spec.ts +33 -10
  304. package/src/components/DatePicker/DatePickerValidationExample/CalendarMode.stories.ts +8 -8
  305. package/src/components/DatePicker/DatePickerValidationExample/ComplexDatePicker.stories.ts +106 -8
  306. package/src/components/DatePicker/DatePickerValidationExample/DateTextInput.stories.ts +12 -11
  307. package/src/components/DatePicker/DatePickerValidationExample/MultiMode.stories.ts +12 -12
  308. package/src/components/DatePicker/DateTextInput/DateRange.stories.ts +0 -12
  309. package/src/components/DatePicker/DateTextInput/DateTextInput.vue +71 -67
  310. package/src/components/DatePicker/DateTextInput/NoCalendar.stories.ts +3 -0
  311. package/src/components/DatePicker/DateTextInput/accessibilite/Accessibility.mdx +66 -0
  312. package/src/components/DatePicker/DateTextInput/tests/DateTextInput.spec.ts +52 -1
  313. package/src/components/DatePicker/composables/index.ts +1 -0
  314. package/src/components/DatePicker/composables/tests/useCalendarKeyboardNavigation.spec.ts +109 -65
  315. package/src/components/DatePicker/composables/tests/useDatePickerFocusTrap.spec.ts +138 -0
  316. package/src/components/DatePicker/composables/tests/useDatePickerState.spec.ts +53 -0
  317. package/src/components/DatePicker/composables/tests/useDateValidation.spec.ts +74 -18
  318. package/src/components/DatePicker/composables/tests/useInputBlurHandler.spec.ts +39 -0
  319. package/src/components/DatePicker/composables/tests/useManualDateValidation.spec.ts +91 -0
  320. package/src/components/DatePicker/composables/useCalendarKeyboardNavigation.ts +442 -36
  321. package/src/components/DatePicker/composables/useDatePickerFocusTrap.ts +92 -0
  322. package/src/components/DatePicker/composables/useDatePickerState.ts +24 -0
  323. package/src/components/DatePicker/composables/useDateTextField.ts +6 -6
  324. package/src/components/DatePicker/composables/useDateValidation.ts +36 -35
  325. package/src/components/DatePicker/composables/useInputBlurHandler.ts +3 -3
  326. package/src/components/DatePicker/composables/useManualDateValidation.ts +6 -2
  327. package/src/components/DatePicker/composables/useMonthButtonCustomization.ts +9 -8
  328. package/src/components/DatePicker/playground/DatePickerHolidayRule.vue +1 -1
  329. package/src/components/DiacriticPicker/accessibilite/Accessibility.mdx +76 -8
  330. package/src/components/DialogBox/DialogBox.vue +3 -5
  331. package/src/components/DialogBox/tests/DialogBox.visual.cy.ts +76 -0
  332. package/src/components/DialogBox/tests/__snapshots__/dialog-box-custom-texts.snap.png +0 -0
  333. package/src/components/DialogBox/tests/__snapshots__/dialog-box-default.snap.png +0 -0
  334. package/src/components/DialogBox/tests/__snapshots__/dialog-box-no-actions.snap.png +0 -0
  335. package/src/components/FileList/FileList.vue +9 -2
  336. package/src/components/FileList/UploadItem/UploadItem.vue +11 -13
  337. package/src/components/FileList/tests/FileList.spec.ts +47 -0
  338. package/src/components/FileUpload/FileUpload.vue +3 -5
  339. package/src/components/FileUpload/FileUploadContent.vue +3 -5
  340. package/src/components/FilterInline/FilterInline.vue +1 -3
  341. package/src/components/FilterSideBar/FilterSideBar.mdx +44 -1
  342. package/src/components/FilterSideBar/FilterSideBar.stories.ts +105 -1
  343. package/src/components/FilterSideBar/FilterSideBar.vue +7 -0
  344. package/src/components/FilterSideBar/tests/FilterSideBar.a11y.spec.ts +54 -1
  345. package/src/components/FilterSideBar/tests/FilterSideBar.spec.ts +42 -0
  346. package/src/components/FooterBar/FooterBar.vue +9 -13
  347. package/src/components/FranceConnectBtn/FranceConnectBtn.vue +1 -1
  348. package/src/components/HeaderBar/HeaderBar.stories.ts +14 -2
  349. package/src/components/HeaderBar/HeaderBar.vue +2 -3
  350. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderBurgerMenu.vue +2 -3
  351. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuItem/HeaderMenuItem.vue +2 -3
  352. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuSection/HeaderMenuSection.vue +0 -1
  353. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderSubMenu/HeaderSubMenu.vue +8 -9
  354. package/src/components/HeaderBar/HeaderBurgerMenu/menu.scss +0 -8
  355. package/src/components/HeaderBar/HeaderBurgerMenu/tests/HeaderBurgerMenu.visual.cy.ts +196 -0
  356. package/src/components/HeaderBar/HeaderBurgerMenu/tests/__snapshots__/header-burger-menu-generated-submenu-open.snap.png +0 -0
  357. package/src/components/HeaderBar/HeaderBurgerMenu/tests/__snapshots__/header-burger-menu-generated.snap.png +0 -0
  358. package/src/components/HeaderBar/HeaderLogo/HeaderLogo.vue +0 -1
  359. package/src/components/HeaderBar/HeaderMenuBtn/HeaderMenuBtn.vue +6 -7
  360. package/src/components/HeaderBar/tests/HeaderBar.visual.cy.ts +81 -0
  361. package/src/components/HeaderBar/tests/__snapshots__/header-bar-custom-width.snap.png +0 -0
  362. package/src/components/HeaderBar/tests/__snapshots__/header-bar-default.snap.png +0 -0
  363. package/src/components/HeaderBar/tests/__snapshots__/header-bar-no-sticky.snap.png +0 -0
  364. package/src/components/HeaderBar/tests/__snapshots__/header-bar-with-prepend.snap.png +0 -0
  365. package/src/components/HeaderBar/tests/__snapshots__/header-bar-with-side.snap.png +0 -0
  366. package/src/components/HeaderBar/tests/__snapshots__/header-bar-with-subtitle.snap.png +0 -0
  367. package/src/components/HeaderNavigationBar/HeaderNavigationBar.vue +12 -3
  368. package/src/components/HeaderNavigationBar/HorizontalNavbar/HorizontalNavbar.vue +15 -8
  369. package/src/components/HeaderToolbar/HeaderToolbar.vue +6 -7
  370. package/src/components/LangBtn/LangBtn.vue +2 -4
  371. package/src/components/Logo/accessibilite/Accessibility.mdx +73 -11
  372. package/src/components/Logo/tests/Logo.visual.cy.ts +57 -0
  373. package/src/components/Logo/tests/__snapshots__/logo-avatar.snap.png +0 -0
  374. package/src/components/Logo/tests/__snapshots__/logo-dark.snap.png +0 -0
  375. package/src/components/Logo/tests/__snapshots__/logo-default.snap.png +0 -0
  376. package/src/components/Logo/tests/__snapshots__/logo-no-organism.snap.png +0 -0
  377. package/src/components/Logo/tests/__snapshots__/logo-no-signature.snap.png +0 -0
  378. package/src/components/Logo/tests/__snapshots__/logo-risque-pro.snap.png +0 -0
  379. package/src/components/LogoBrandSection/LogoBrandSection.vue +2 -2
  380. package/src/components/LogoBrandSection/accessibilite/Accessibility.mdx +85 -9
  381. package/src/components/LunarCalendar/tests/LunarCalendar.spec.ts +3 -1
  382. package/src/components/LunarCalendar/useLunarCalendarValidation.ts +4 -5
  383. package/src/components/MonthPicker/MonthPickerVisual/VisualPickerHeader.vue +1 -1
  384. package/src/components/MonthPicker/tests/MonthPicker.spec.ts +2 -1
  385. package/src/components/MonthPicker/tests/__snapshots__/MonthPicker.spec.ts.snap +7 -7
  386. package/src/components/NirField/NirField.stories.ts +6 -2
  387. package/src/components/NirField/NirField.vue +67 -265
  388. package/src/components/NirField/accessibilite/Accessibility.mdx +102 -7
  389. package/src/components/NirField/locales.ts +1 -1
  390. package/src/components/NirField/tests/NirField.spec.ts +124 -0
  391. package/src/components/NirField/tests/useNirValidation.spec.ts +449 -0
  392. package/src/components/NirField/useNirValidation.ts +277 -0
  393. package/src/components/NotificationBar/Notification/Notification.vue +5 -7
  394. package/src/components/NotificationBar/NotificationBar.vue +1 -3
  395. package/src/components/PaginatedTable/PaginatedTable.vue +2 -3
  396. package/src/components/PaginatedTable/Pagination.vue +3 -5
  397. package/src/components/PasswordField/PasswordField.stories.ts +4 -4
  398. package/src/components/PasswordField/PasswordField.vue +26 -34
  399. package/src/components/PasswordField/tests/PasswordField.spec.ts +6 -3
  400. package/src/components/PeriodField/PeriodField.stories.ts +4 -4
  401. package/src/components/PeriodField/PeriodField.vue +57 -57
  402. package/src/components/PeriodField/__tests__/PeriodField.async.spec.ts +32 -0
  403. package/src/components/PeriodField/accessibilite/Accessibility.mdx +68 -8
  404. package/src/components/PeriodField/tests/PeriodField.spec.ts +28 -2
  405. package/src/components/PhoneField/PhoneField.vue +8 -9
  406. package/src/components/PhoneField/tests/PhoneField.spec.ts +1 -0
  407. package/src/components/RangeField/RangeField.vue +6 -0
  408. package/src/components/RangeField/RangeSlider/RangeSlider.vue +0 -2
  409. package/src/components/RangeField/RangeSlider/Tooltip/Tooltip.vue +1 -1
  410. package/src/components/RangeField/tests/RangeField.visual.cy.ts +65 -0
  411. package/src/components/RangeField/tests/__snapshots__/range-field-custom-bg.snap.png +0 -0
  412. package/src/components/RangeField/tests/__snapshots__/range-field-custom-range.snap.png +0 -0
  413. package/src/components/RangeField/tests/__snapshots__/range-field-default.snap.png +0 -0
  414. package/src/components/RangeField/tests/__snapshots__/range-field-step.snap.png +0 -0
  415. package/src/components/RangeField/tests/__snapshots__/range-field-with-label.snap.png +0 -0
  416. package/src/components/RatingPicker/EmotionPicker/EmotionPicker.vue +6 -8
  417. package/src/components/RatingPicker/StarsPicker/StarsPicker.vue +3 -5
  418. package/src/components/SearchListField/SearchListField.vue +0 -2
  419. package/src/components/SkipLink/SkipLink.vue +2 -4
  420. package/src/components/SocialMediaLinks/SocialMediaLinks.vue +6 -6
  421. package/src/components/SubHeader/SubHeader.vue +1 -1
  422. package/src/components/SyAlert/SyAlert.vue +7 -9
  423. package/src/components/SyAlert/tests/SyAlert.visual.cy.ts +46 -0
  424. package/src/components/SyAlert/tests/__snapshots__/sy-alert-closable.snap.png +0 -0
  425. package/src/components/SyAlert/tests/__snapshots__/sy-alert-error.snap.png +0 -0
  426. package/src/components/SyAlert/tests/__snapshots__/sy-alert-info.snap.png +0 -0
  427. package/src/components/SyAlert/tests/__snapshots__/sy-alert-success.snap.png +0 -0
  428. package/src/components/SyAlert/tests/__snapshots__/sy-alert-variant-outlined.snap.png +0 -0
  429. package/src/components/SyAlert/tests/__snapshots__/sy-alert-variant-tonal.snap.png +0 -0
  430. package/src/components/SyAlert/tests/__snapshots__/sy-alert-warning.snap.png +0 -0
  431. package/src/components/SyBtnMenu/SyBtnMenu.vue +2 -4
  432. package/src/components/SyTextArea/SyTextArea.stories.ts +138 -2
  433. package/src/components/SyTextArea/SyTextArea.vue +53 -23
  434. package/src/components/SyTextArea/tests/SyTextArea.spec.ts +126 -3
  435. package/src/components/SyTextArea/useDefaultValidationRules.ts +74 -0
  436. package/src/components/TableToolbar/TableToolbar.vue +6 -8
  437. package/src/components/Tables/SyServerTable/SyServerTable.mdx +25 -0
  438. package/src/components/Tables/SyServerTable/SyServerTable.stories.ts +839 -1
  439. package/src/components/Tables/SyServerTable/SyServerTable.vue +154 -95
  440. package/src/components/Tables/SyServerTable/tests/SyServerTable.a11y.spec.ts +81 -0
  441. package/src/components/Tables/SyServerTable/tests/SyServerTable.spec.ts +390 -0
  442. package/src/components/Tables/SyTable/SyTable.mdx +25 -0
  443. package/src/components/Tables/SyTable/SyTable.stories.ts +556 -1
  444. package/src/components/Tables/SyTable/SyTable.vue +136 -60
  445. package/src/components/Tables/SyTable/tests/SyTable.a11y.spec.ts +79 -0
  446. package/src/components/Tables/SyTable/tests/SyTable.spec.ts +382 -0
  447. package/src/components/Tables/common/SyTableFilter.vue +49 -4
  448. package/src/components/Tables/common/SyTablePagination.vue +142 -19
  449. package/src/components/Tables/common/TableHeader.vue +45 -4
  450. package/src/components/Tables/common/filters/AutocompleteFilter.vue +160 -0
  451. package/src/components/Tables/common/filters/NumberFilter.vue +1 -1
  452. package/src/components/Tables/common/filters/SelectFilter.vue +11 -12
  453. package/src/components/Tables/common/filters/TextFilter.vue +1 -1
  454. package/src/components/Tables/common/filters/getFilterComponent.ts +8 -1
  455. package/src/components/Tables/common/filters/locales.ts +0 -1
  456. package/src/components/Tables/common/filters/tests/AutocompleteFilter.a11y.spec.ts +110 -0
  457. package/src/components/Tables/common/filters/tests/AutocompleteFilter.spec.ts +203 -0
  458. package/src/components/Tables/common/filters/tests/SelectFilter.a11y.spec.ts +104 -0
  459. package/src/components/Tables/common/filters/tests/SelectFilter.spec.ts +152 -16
  460. package/src/components/Tables/common/locales.ts +3 -0
  461. package/src/components/Tables/common/tableFilterUtils.ts +3 -0
  462. package/src/components/Tables/common/tableStyles.scss +60 -19
  463. package/src/components/Tables/common/tests/SyTablePagination.spec.ts +18 -0
  464. package/src/components/Tables/common/tests/TableHeader.spec.ts +39 -0
  465. package/src/components/Tables/common/tests/filterByRange.spec.ts +2 -1
  466. package/src/components/Tables/common/types.ts +15 -4
  467. package/src/components/Tables/common/useClickableTableRow.ts +103 -0
  468. package/src/components/Tables/common/usePagination.ts +13 -0
  469. package/src/components/Tables/common/usePinnedColumns.ts +237 -0
  470. package/src/components/Tables/common/useTableHeaders.ts +52 -30
  471. package/src/components/Tables/common/useTableRowCheckboxAccessibility.ts +41 -0
  472. package/src/components/UploadWorkflow/UploadWorkflow.vue +1 -0
  473. package/src/components/UploadWorkflow/tests/UploadWorkflow.spec.ts +23 -0
  474. package/src/components/UploadWorkflow/tests/__snapshots__/UploadWorkflow.spec.ts.snap +66 -0
  475. package/src/components/UploadWorkflow/useFileList.ts +3 -0
  476. package/src/components/UserMenuBtn/UserMenuBtn.vue +1 -3
  477. package/src/composables/date/tests/useDatePickerAccessibility.spec.ts +2 -6
  478. package/src/composables/date/useDatePickerAccessibility.ts +42 -207
  479. package/src/composables/rules/tests/useFieldValidation.spec.ts +120 -120
  480. package/src/composables/rules/useFieldValidation.ts +34 -17
  481. package/src/composables/unifyValidation/documentationValidationProps.ts +235 -0
  482. package/src/composables/unifyValidation/tests/useCustomValidation.spec.ts +601 -0
  483. package/src/composables/unifyValidation/tests/useValidation.spec.ts +2048 -0
  484. package/src/composables/unifyValidation/tests/useVuetifyValidation.spec.ts +184 -0
  485. package/src/composables/unifyValidation/useCustomValidation.ts +95 -0
  486. package/src/composables/unifyValidation/useValidation.ts +190 -0
  487. package/src/composables/unifyValidation/useVuetifyValidation.ts +54 -0
  488. package/src/composables/useFormFieldErrorHandling.ts +4 -7
  489. package/src/composables/validation/tests/useFormValidation.spec.ts +14 -0
  490. package/src/composables/validation/tests/useValidation.spec.ts +116 -21
  491. package/src/composables/validation/useFormValidation.ts +20 -13
  492. package/src/composables/validation/useValidatable.ts +8 -1
  493. package/src/composables/validation/useValidation.ts +151 -99
  494. package/src/composantsVuetify/Introduction.mdx +48 -0
  495. package/src/composantsVuetify/VBreadcrumbs/VBreadcrumbs.mdx +28 -0
  496. package/src/composantsVuetify/VBreadcrumbs/v-breadcrumbs.stories.ts +108 -0
  497. package/src/composantsVuetify/VBtn/VBtn.mdx +72 -0
  498. package/src/composantsVuetify/VBtn/v-btn.stories.ts +121 -0
  499. package/src/composantsVuetify/VOtpInput/VOtpInput.mdx +39 -0
  500. package/src/composantsVuetify/VOtpInput/v-otp-input.stories.ts +56 -0
  501. package/src/composantsVuetify/VSkeletonLoader/VSkeletonLoader.mdx +42 -0
  502. package/src/composantsVuetify/VSkeletonLoader/v-skeleton-loader.stories.ts +77 -0
  503. package/src/composantsVuetify/VSwitch/VSwitch.mdx +47 -0
  504. package/src/composantsVuetify/VSwitch/v-switch.stories.ts +166 -0
  505. package/src/composantsVuetify/VTooltip/VTooltip.mdx +32 -0
  506. package/src/composantsVuetify/VTooltip/v-tooltip.stories.ts +95 -0
  507. package/src/designTokens/tests/buildColorClassMap.spec.ts +31 -0
  508. package/src/designTokens/tests/generateScssTokens.spec.ts +12 -0
  509. package/src/designTokens/tests/themeUtils.spec.ts +53 -0
  510. package/src/designTokens/tokens/amelipro/apColors.ts +8 -130
  511. package/src/designTokens/tokens/amelipro/apColors2026.ts +3 -15
  512. package/src/designTokens/tokens/amelipro/apContextual.ts +55 -47
  513. package/src/designTokens/tokens/amelipro/apLightTheme.ts +1 -1
  514. package/src/designTokens/tokens/amelipro/apSemantic.ts +1 -1
  515. package/src/designTokens/tokens/baseColors.ts +129 -0
  516. package/src/designTokens/tokens/baseContextualTokens.ts +52 -0
  517. package/src/designTokens/tokens/cnam/cnamColors.ts +3 -125
  518. package/src/designTokens/tokens/cnam/cnamContextual.ts +4 -48
  519. package/src/designTokens/tokens/cnam/cnamLightTheme.ts +1 -1
  520. package/src/designTokens/tokens/cnam/cnamSemantic.ts +3 -3
  521. package/src/designTokens/tokens/pa/paColors.ts +2 -166
  522. package/src/designTokens/tokens/pa/paContextual.ts +3 -48
  523. package/src/designTokens/tokens/pa/paLightTheme.ts +1 -1
  524. package/src/designTokens/tokens/pa/paSemantic.ts +2 -2
  525. package/src/designTokens/utils/buildColorClassMap.ts +34 -0
  526. package/src/designTokens/utils/convertSemanticsToken.ts +8 -11
  527. package/src/designTokens/utils/createFlattenTheme.ts +15 -7
  528. package/src/designTokens/utils/index.ts +2 -2
  529. package/src/main.ts +0 -2
  530. package/src/stories/Accessibilite/AuditDesignSystem.mdx +0 -11
  531. package/src/stories/Accessibilite/DesignSystem/Avancement.mdx +433 -0
  532. package/src/stories/Accessibilite/DesignSystem/a11y-status.json +692 -0
  533. package/src/stories/Accessibilite/KitDePreAudit/Echantillonnage.mdx +8 -1
  534. package/src/stories/Accessibilite/KitDePreAudit/Introduction.mdx +51 -10
  535. package/src/stories/Accessibilite/KitDePreAudit/Outils/Introduction.mdx +16 -9
  536. package/src/stories/Accessibilite/KitDePreAudit/Preaudit.mdx +48 -58
  537. package/src/stories/Components/Components.stories.ts +113 -9
  538. package/src/stories/Demarrer/Accueil.stories.ts +3 -3
  539. package/src/stories/Demarrer/EnrichirLeDesignSystem.mdx +4 -9
  540. package/src/stories/Demarrer/EnrichirLeDesignSystem.stories.ts +28 -0
  541. package/src/stories/Demarrer/Releases.stories.ts +48 -5
  542. package/src/stories/DesignTokens/ColorDisplay.vue +6 -5
  543. package/src/stories/DesignTokens/ColorIntegrationExample.vue +2 -4
  544. package/src/stories/DesignTokens/colors.stories.ts +5 -6
  545. package/src/stories/GuideDuDev/Amelipro.mdx +15 -0
  546. package/src/stories/GuideDuDev/Amelipro.stories.ts +209 -0
  547. package/src/stories/GuideDuDev/CreateVuetifyInstance.mdx +95 -0
  548. package/src/stories/GuideDuDev/Theme.mdx +36 -26
  549. package/src/stories/GuideDuDev/moduleDeNotification.mdx +3 -2
  550. package/src/stories/GuideDuDev/vuetifyOptions.mdx +3 -3
  551. package/src/stories/styles/accessibility-guide.css +3 -3
  552. package/src/utils/functions/classToHex.ts +6 -34
  553. package/src/utils/functions/createHexResolver.ts +45 -0
  554. package/src/utils/functions/tests/classToHex.spec.ts +36 -0
  555. package/src/utils/functions/tests/convertToHex.spec.ts +31 -0
  556. package/src/utils/functions/tests/createHexResolver.spec.ts +66 -0
  557. package/src/utils/functions/tests/isCssColor.spec.ts +48 -0
  558. package/dist/SelectFilter-BioGT6Nn.js +0 -136
  559. package/dist/designTokens/utils/convertGaps.d.ts +0 -5
  560. package/dist/main-aLKwdMi1.js +0 -37886
  561. package/dist/style.css +0 -1
  562. package/src/assets/apTokens.scss +0 -343
  563. package/src/assets/overrides/_container.scss +0 -36
  564. package/src/assets/tokens.scss +0 -388
  565. package/src/components/DatePicker/Accessibilite.mdx +0 -14
  566. package/src/designTokens/apColors.md +0 -66
  567. package/src/designTokens/cnamColors.md +0 -193
  568. package/src/designTokens/paColors.md +0 -66
  569. package/src/designTokens/tokens/json/contextual-tokens.json +0 -156
  570. package/src/designTokens/tokens/json/primitives.json +0 -209
  571. package/src/designTokens/tokens/json/semantic.json +0 -120
  572. package/src/designTokens/utils/convertGaps.ts +0 -11
  573. package/src/stories/Accessibilite/Avancement/Avancement.mdx +0 -533
  574. package/src/stories/Accessibilite/Avancement/Avancement.stories.ts +0 -306
@@ -0,0 +1,2048 @@
1
+ /* eslint-disable vue/one-component-per-file */
2
+ import { describe, it, expect } from 'vitest'
3
+ import { defineComponent, nextTick, ref } from 'vue'
4
+ import { mount } from '@vue/test-utils'
5
+ import { useValidation } from '../useValidation'
6
+ import SyForm from '@/components/Customs/SyForm/SyForm.vue'
7
+ import type { ValidationRule } from '@/composables/validation/useValidation'
8
+
9
+ /** Run a composable inside a mounted Vue component to support lifecycle hooks. */
10
+ function withSetup<T>(setup: () => T): { result: T, wrapper: ReturnType<typeof mount> } {
11
+ let result: T
12
+ const TestComponent = defineComponent({
13
+ setup() {
14
+ result = setup()
15
+ return {}
16
+ },
17
+ render: () => null,
18
+ })
19
+ const wrapper = mount(TestComponent)
20
+ return { result: result!, wrapper }
21
+ }
22
+
23
+ describe('useValidation (unifyValidation)', () => {
24
+ const makeParams = (overrides = {}) => ({
25
+ modelValue: ref<unknown>(''),
26
+ readonly: ref(false),
27
+ disabled: ref(false),
28
+ required: ref(false),
29
+ isValidateOnBlur: ref(true),
30
+ showSuccessMessages: ref(true),
31
+ disableErrorHandling: ref(false),
32
+ label: ref('Mon champ'),
33
+ focused: ref(false),
34
+ useVuetifyValidation: false as const,
35
+ customRules: ref<ValidationRule[]>([]),
36
+ customWarningRules: ref<ValidationRule[]>([]),
37
+ customSuccessRules: ref<ValidationRule[]>([]),
38
+ ...overrides,
39
+ })
40
+
41
+ describe('disableErrorHandling', () => {
42
+ it('returns a stub with empty refs and false computed values when disableErrorHandling is true', () => {
43
+ const params = makeParams({ disableErrorHandling: ref(true) })
44
+ const result = useValidation(params as Parameters<typeof useValidation>[0])
45
+
46
+ expect(result.errors.value).toEqual([])
47
+ expect(result.warnings.value).toEqual([])
48
+ expect(result.successes.value).toEqual([])
49
+ expect(result.hasError.value).toBe(false)
50
+ expect(result.hasWarning.value).toBe(false)
51
+ expect(result.hasSuccess.value).toBe(false)
52
+ })
53
+
54
+ it('stub validate() always returns true when disableErrorHandling is true', async () => {
55
+ const params = makeParams({ disableErrorHandling: ref(true) })
56
+ const result = useValidation(params as Parameters<typeof useValidation>[0])
57
+
58
+ const valid = await result.validate()
59
+ expect(valid).toBe(true)
60
+ })
61
+ })
62
+
63
+ describe('errorMessages / warningMessages / successMessages props', () => {
64
+ it('syncs errorMessages to errors ref immediately via watch', async () => {
65
+ const errorMessages = ref<string[] | null>(['Une erreur externe'])
66
+ const params = makeParams({ errorMessages })
67
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
68
+
69
+ await nextTick()
70
+ expect(result.errors.value).toContain('Une erreur externe')
71
+ })
72
+
73
+ it('syncs warningMessages to warnings ref immediately', async () => {
74
+ const warningMessages = ref<string[] | null>(['Un avertissement'])
75
+ const params = makeParams({ warningMessages })
76
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
77
+
78
+ await nextTick()
79
+ expect(result.warnings.value).toContain('Un avertissement')
80
+ })
81
+
82
+ it('syncs successMessages to successes ref immediately', async () => {
83
+ const successMessages = ref<string[] | null>(['Succès !'])
84
+ const params = makeParams({ successMessages })
85
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
86
+
87
+ await nextTick()
88
+ expect(result.successes.value).toContain('Succès !')
89
+ })
90
+
91
+ it('clears errors when errorMessages is set to null', async () => {
92
+ const errorMessages = ref<string[] | null>(['Erreur'])
93
+ const params = makeParams({ errorMessages })
94
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
95
+
96
+ await nextTick()
97
+ expect(result.errors.value).toContain('Erreur')
98
+
99
+ errorMessages.value = null
100
+ await nextTick()
101
+ expect(result.errors.value).toEqual([])
102
+ })
103
+
104
+ it('updates errors when errorMessages change reactively', async () => {
105
+ const errorMessages = ref<string[] | null>(null)
106
+ const params = makeParams({ errorMessages })
107
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
108
+
109
+ await nextTick()
110
+ expect(result.errors.value).toEqual([])
111
+
112
+ errorMessages.value = ['Nouvelle erreur']
113
+ await nextTick()
114
+ expect(result.errors.value).toContain('Nouvelle erreur')
115
+ })
116
+
117
+ it('combines rule validation errors with external errorMessages', async () => {
118
+ const errorMessages = ref<string[] | null>(['Erreur par défaut'])
119
+ const params = makeParams({
120
+ modelValue: ref(''),
121
+ errorMessages,
122
+ customRules: ref([{ type: 'required', options: { message: 'Erreur règle' } }]),
123
+ })
124
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
125
+
126
+ const valid = await result.validate()
127
+ expect(valid).toBe(false)
128
+ expect(result.errors.value).toEqual(['Erreur règle', 'Erreur par défaut'])
129
+ expect(result.hasError.value).toBe(true)
130
+ })
131
+
132
+ it('combines Vuetify rule errors with external errorMessages when useVuetifyValidation is true', async () => {
133
+ const params = {
134
+ modelValue: ref<unknown>(''),
135
+ readonly: ref(false),
136
+ disabled: ref(false),
137
+ required: ref(false),
138
+ isValidateOnBlur: ref(true),
139
+ showSuccessMessages: ref(true),
140
+ disableErrorHandling: ref(false),
141
+ label: ref('Mon champ'),
142
+ focused: ref(false),
143
+ useVuetifyValidation: true as const,
144
+ rules: ref([(v: unknown) => !!v || 'Erreur règle vuetify']),
145
+ errorMessages: ref<string[] | null>(['Erreur par défaut']),
146
+ maxErrors: ref(2),
147
+ }
148
+ const { result } = withSetup(() => useValidation(params))
149
+
150
+ const valid = await result.validate()
151
+ expect(valid).toBe(false)
152
+ expect(result.errors.value).toContain('Erreur règle vuetify')
153
+ expect(result.errors.value).toContain('Erreur par défaut')
154
+ expect(result.hasError.value).toBe(true)
155
+ })
156
+ })
157
+
158
+ describe('validate()', () => {
159
+ describe('when useVuetifyValidation = false', () => {
160
+ it('returns true and clears state when readonly is true', async () => {
161
+ const params = makeParams({
162
+ readonly: ref(false),
163
+ customRules: ref([{ type: 'required', options: { message: 'Requis readonly' } }]),
164
+ modelValue: ref(''),
165
+ })
166
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
167
+
168
+ const invalid = await result.validate()
169
+ expect(invalid).toBe(false)
170
+ expect(result.errors.value).toContain('Requis readonly')
171
+
172
+ params.readonly.value = true
173
+ const valid = await result.validate()
174
+
175
+ expect(valid).toBe(true)
176
+ expect(result.errors.value).toEqual([])
177
+ })
178
+
179
+ it('returns true and clears state when disabled is true', async () => {
180
+ const params = makeParams({
181
+ disabled: ref(false),
182
+ customRules: ref([{ type: 'required', options: { message: 'Requis disabled' } }]),
183
+ modelValue: ref(''),
184
+ })
185
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
186
+
187
+ const invalid = await result.validate()
188
+ expect(invalid).toBe(false)
189
+ expect(result.errors.value).toContain('Requis disabled')
190
+
191
+ params.disabled.value = true
192
+ const valid = await result.validate()
193
+
194
+ expect(valid).toBe(true)
195
+ expect(result.errors.value).toEqual([])
196
+ })
197
+
198
+ it('returns true when custom required rule passes (non-empty value)', async () => {
199
+ const params = makeParams({
200
+ useVuetifyValidation: false as const,
201
+ customRules: ref([{ type: 'required', options: { message: 'Requis' } }]),
202
+ modelValue: ref('bonjour'),
203
+ })
204
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
205
+
206
+ const valid = await result.validate()
207
+ expect(valid).toBe(true)
208
+ expect(result.errors.value).toEqual([])
209
+ })
210
+
211
+ it('returns false and populates errors when custom required rule fails (empty value)', async () => {
212
+ const params = makeParams({
213
+ useVuetifyValidation: false as const,
214
+ customRules: ref([{ type: 'required', options: { message: 'Requis' } }]),
215
+ modelValue: ref(''),
216
+ })
217
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
218
+
219
+ const valid = await result.validate()
220
+ expect(valid).toBe(false)
221
+ expect(result.errors.value).toContain('Requis')
222
+ })
223
+
224
+ it('supports custom rules with validate function in options (sync)', async () => {
225
+ const params = makeParams({
226
+ useVuetifyValidation: false as const,
227
+ customRules: ref([
228
+ {
229
+ type: 'custom',
230
+ options: {
231
+ validate: (value: unknown) => value === 'ok',
232
+ message: 'Valeur personnalisée invalide',
233
+ },
234
+ },
235
+ ]),
236
+ modelValue: ref('ko'),
237
+ })
238
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
239
+
240
+ const valid = await result.validate()
241
+ expect(valid).toBe(false)
242
+ expect(result.errors.value).toContain('Valeur personnalisée invalide')
243
+ })
244
+
245
+ it('supports custom rules with validate function in options (async)', async () => {
246
+ const params = makeParams({
247
+ useVuetifyValidation: false as const,
248
+ customRules: ref([
249
+ {
250
+ type: 'custom',
251
+ options: {
252
+ validate: async (value: unknown) => value === 'ok',
253
+ message: 'Erreur async',
254
+ },
255
+ },
256
+ ]),
257
+ modelValue: ref('ko'),
258
+ })
259
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
260
+
261
+ const valid = await result.validate()
262
+ expect(valid).toBe(false)
263
+ expect(result.errors.value).toContain('Erreur async')
264
+ })
265
+
266
+ it('populates warnings from custom warning rules', async () => {
267
+ const params = makeParams({
268
+ useVuetifyValidation: false as const,
269
+ customRules: ref([]),
270
+ customWarningRules: ref([
271
+ {
272
+ type: 'custom',
273
+ options: {
274
+ validate: (value: unknown) => value === 'ok',
275
+ warningMessage: 'Warning custom',
276
+ isWarning: true,
277
+ },
278
+ },
279
+ ]),
280
+ modelValue: ref('ko'),
281
+ })
282
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
283
+
284
+ const valid = await result.validate()
285
+ expect(valid).toBe(true)
286
+ expect(result.warnings.value).toContain('Warning custom')
287
+ })
288
+
289
+ it('populates successes from custom success rules', async () => {
290
+ const params = makeParams({
291
+ useVuetifyValidation: false as const,
292
+ customRules: ref([]),
293
+ customSuccessRules: ref([
294
+ {
295
+ type: 'custom',
296
+ options: {
297
+ validate: (value: unknown) => value === 'ok',
298
+ successMessage: 'Succès custom',
299
+ },
300
+ },
301
+ ]),
302
+ modelValue: ref('ok'),
303
+ })
304
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
305
+
306
+ const valid = await result.validate()
307
+ expect(valid).toBe(true)
308
+ expect(result.successes.value).toContain('Succès custom')
309
+ })
310
+
311
+ describe('blur vs input validation triggers', () => {
312
+ it('with isValidateOnBlur=true, does not validate while focused then validates on blur', async () => {
313
+ const params = makeParams({
314
+ useVuetifyValidation: false as const,
315
+ isValidateOnBlur: ref(true),
316
+ focused: ref(true),
317
+ modelValue: ref(''),
318
+ customRules: ref([{ type: 'required', options: { message: 'Requis blur' } }]),
319
+ })
320
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
321
+
322
+ await nextTick()
323
+ expect(result.errors.value).toEqual([])
324
+
325
+ params.focused.value = false
326
+ await nextTick()
327
+ expect(result.errors.value).toContain('Requis blur')
328
+ })
329
+
330
+ it('with isValidateOnBlur=false, does not validate while focused then validates on blur', async () => {
331
+ const params = makeParams({
332
+ useVuetifyValidation: false as const,
333
+ isValidateOnBlur: ref(false),
334
+ focused: ref(true),
335
+ modelValue: ref(''),
336
+ customRules: ref([{ type: 'required', options: { message: 'Requis blur input-mode' } }]),
337
+ })
338
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
339
+
340
+ await nextTick()
341
+ expect(result.errors.value).toEqual([])
342
+
343
+ params.focused.value = false
344
+ await nextTick()
345
+ expect(result.errors.value).toContain('Requis blur input-mode')
346
+ })
347
+ })
348
+
349
+ describe('input events', () => {
350
+ it('with isValidateOnBlur=false, validates automatically on input change', async () => {
351
+ const params = makeParams({
352
+ useVuetifyValidation: false as const,
353
+ isValidateOnBlur: ref(false),
354
+ modelValue: ref('valeur initiale'),
355
+ customRules: ref([{ type: 'required', options: { message: 'Requis input event' } }]),
356
+ })
357
+ let result!: ReturnType<typeof useValidation>
358
+ const wrapper = mount(defineComponent({
359
+ setup() {
360
+ result = useValidation(params as Parameters<typeof useValidation>[0])
361
+ const onInput = (event: Event) => {
362
+ params.modelValue.value = (event.target as HTMLInputElement).value
363
+ }
364
+
365
+ return {
366
+ modelValue: params.modelValue,
367
+ onInput,
368
+ }
369
+ },
370
+ template: `<input data-test="field" :value="modelValue" @input="onInput">`,
371
+ }))
372
+
373
+ await nextTick()
374
+ expect(result.errors.value).toEqual([])
375
+
376
+ await wrapper.get('[data-test="field"]').setValue('')
377
+ await nextTick()
378
+ expect(result.errors.value).toContain('Requis input event')
379
+
380
+ wrapper.unmount()
381
+ })
382
+
383
+ it('with isValidateOnBlur=true, does not validate automatically on input change', async () => {
384
+ const params = makeParams({
385
+ useVuetifyValidation: false as const,
386
+ isValidateOnBlur: ref(true),
387
+ modelValue: ref('valeur initiale'),
388
+ focused: ref(false),
389
+ customRules: ref([{ type: 'required', options: { message: 'Requis on blur only' } }]),
390
+ })
391
+ let result!: ReturnType<typeof useValidation>
392
+ const wrapper = mount(defineComponent({
393
+ setup() {
394
+ result = useValidation(params as Parameters<typeof useValidation>[0])
395
+ const onInput = (event: Event) => {
396
+ params.modelValue.value = (event.target as HTMLInputElement).value
397
+ }
398
+ const onFocus = () => {
399
+ params.focused.value = true
400
+ }
401
+ const onBlur = () => {
402
+ params.focused.value = false
403
+ }
404
+
405
+ return {
406
+ modelValue: params.modelValue,
407
+ onInput,
408
+ onFocus,
409
+ onBlur,
410
+ }
411
+ },
412
+ template: `<input data-test="field" :value="modelValue" @input="onInput" @focus="onFocus" @blur="onBlur">`,
413
+ }))
414
+
415
+ await wrapper.get('[data-test="field"]').trigger('focus')
416
+ await wrapper.get('[data-test="field"]').setValue('')
417
+ await nextTick()
418
+ expect(result.errors.value).toEqual([])
419
+
420
+ await wrapper.get('[data-test="field"]').trigger('blur')
421
+ await nextTick()
422
+ expect(result.errors.value).toContain('Requis on blur only')
423
+
424
+ wrapper.unmount()
425
+ })
426
+ })
427
+ })
428
+
429
+ describe('when useVuetifyValidation = true', () => {
430
+ it('returns true when Vuetify required rule passes (non-empty value)', async () => {
431
+ const params = {
432
+ modelValue: ref<unknown>('bonjour'),
433
+ readonly: ref(false),
434
+ disabled: ref(false),
435
+ required: ref(false),
436
+ isValidateOnBlur: ref(true),
437
+ showSuccessMessages: ref(true),
438
+ disableErrorHandling: ref(false),
439
+ label: ref('Mon champ'),
440
+ focused: ref(false),
441
+ useVuetifyValidation: true as const,
442
+ rules: ref([(v: unknown) => !!v || 'Requis Vuetify']),
443
+ maxErrors: ref(1),
444
+ }
445
+ const { result } = withSetup(() => useValidation(params))
446
+
447
+ const valid = await result.validate()
448
+ expect(valid).toBe(true)
449
+ })
450
+
451
+ it('returns false when Vuetify required rule fails (empty value)', async () => {
452
+ const params = {
453
+ modelValue: ref<unknown>(''),
454
+ readonly: ref(false),
455
+ disabled: ref(false),
456
+ required: ref(false),
457
+ isValidateOnBlur: ref(true),
458
+ showSuccessMessages: ref(true),
459
+ disableErrorHandling: ref(false),
460
+ label: ref('Mon champ'),
461
+ focused: ref(false),
462
+ useVuetifyValidation: true as const,
463
+ rules: ref([(v: unknown) => !!v || 'Requis Vuetify']),
464
+ maxErrors: ref(1),
465
+ }
466
+ const { result } = withSetup(() => useValidation(params))
467
+
468
+ const valid = await result.validate()
469
+ expect(valid).toBe(false)
470
+ })
471
+
472
+ it('returns false when Vuetify custom sync rule fails', async () => {
473
+ const params = {
474
+ modelValue: ref<unknown>('ko'),
475
+ readonly: ref(false),
476
+ disabled: ref(false),
477
+ required: ref(false),
478
+ isValidateOnBlur: ref(true),
479
+ showSuccessMessages: ref(true),
480
+ disableErrorHandling: ref(false),
481
+ label: ref('Mon champ'),
482
+ focused: ref(false),
483
+ useVuetifyValidation: true as const,
484
+ rules: ref([(v: unknown) => v === 'ok' || 'Erreur sync Vuetify']),
485
+ maxErrors: ref(1),
486
+ }
487
+ const { result } = withSetup(() => useValidation(params))
488
+
489
+ const valid = await result.validate()
490
+ expect(valid).toBe(false)
491
+ expect(result.errors.value).toContain('Erreur sync Vuetify')
492
+ })
493
+
494
+ it('returns true when Vuetify custom sync rule passes', async () => {
495
+ const params = {
496
+ modelValue: ref<unknown>('ok'),
497
+ readonly: ref(false),
498
+ disabled: ref(false),
499
+ required: ref(false),
500
+ isValidateOnBlur: ref(true),
501
+ showSuccessMessages: ref(true),
502
+ disableErrorHandling: ref(false),
503
+ label: ref('Mon champ'),
504
+ focused: ref(false),
505
+ useVuetifyValidation: true as const,
506
+ rules: ref([(v: unknown) => v === 'ok' || 'Erreur sync Vuetify']),
507
+ maxErrors: ref(1),
508
+ }
509
+ const { result } = withSetup(() => useValidation(params))
510
+
511
+ const valid = await result.validate()
512
+ expect(valid).toBe(true)
513
+ expect(result.errors.value).toEqual([])
514
+ })
515
+
516
+ it('returns false when Vuetify custom async rule fails', async () => {
517
+ const params = {
518
+ modelValue: ref<unknown>('ko'),
519
+ readonly: ref(false),
520
+ disabled: ref(false),
521
+ required: ref(false),
522
+ isValidateOnBlur: ref(true),
523
+ showSuccessMessages: ref(true),
524
+ disableErrorHandling: ref(false),
525
+ label: ref('Mon champ'),
526
+ focused: ref(false),
527
+ useVuetifyValidation: true as const,
528
+ rules: ref([async (v: unknown) => v === 'ok' || 'Erreur async Vuetify']),
529
+ maxErrors: ref(1),
530
+ }
531
+ const { result } = withSetup(() => useValidation(params))
532
+
533
+ const valid = await result.validate()
534
+ expect(valid).toBe(false)
535
+ expect(result.errors.value).toContain('Erreur async Vuetify')
536
+ })
537
+
538
+ it('returns true when Vuetify custom async rule passes', async () => {
539
+ const params = {
540
+ modelValue: ref<unknown>('ok'),
541
+ readonly: ref(false),
542
+ disabled: ref(false),
543
+ required: ref(false),
544
+ isValidateOnBlur: ref(true),
545
+ showSuccessMessages: ref(true),
546
+ disableErrorHandling: ref(false),
547
+ label: ref('Mon champ'),
548
+ focused: ref(false),
549
+ useVuetifyValidation: true as const,
550
+ rules: ref([async (v: unknown) => v === 'ok' || 'Erreur async Vuetify']),
551
+ maxErrors: ref(1),
552
+ }
553
+ const { result } = withSetup(() => useValidation(params))
554
+
555
+ const valid = await result.validate()
556
+ expect(valid).toBe(true)
557
+ expect(result.errors.value).toEqual([])
558
+ })
559
+
560
+ describe('input events', () => {
561
+ it('with isValidateOnBlur=false, validates automatically on input change', async () => {
562
+ const params = {
563
+ modelValue: ref<unknown>('valeur initiale'),
564
+ readonly: ref(false),
565
+ disabled: ref(false),
566
+ required: ref(false),
567
+ isValidateOnBlur: ref(false),
568
+ showSuccessMessages: ref(true),
569
+ disableErrorHandling: ref(false),
570
+ label: ref('Mon champ'),
571
+ focused: ref(false),
572
+ useVuetifyValidation: true as const,
573
+ rules: ref([(v: unknown) => !!v || 'Requis Vuetify input event']),
574
+ maxErrors: ref(1),
575
+ }
576
+ let result!: ReturnType<typeof useValidation>
577
+ const wrapper = mount(defineComponent({
578
+ setup() {
579
+ result = useValidation(params)
580
+ const onInput = (event: Event) => {
581
+ params.modelValue.value = (event.target as HTMLInputElement).value
582
+ }
583
+
584
+ return {
585
+ modelValue: params.modelValue,
586
+ onInput,
587
+ }
588
+ },
589
+ template: `<input data-test="vuetify-field" :value="modelValue" @input="onInput">`,
590
+ }))
591
+
592
+ await nextTick()
593
+ expect(result.errors.value).toEqual([])
594
+
595
+ await wrapper.get('[data-test="vuetify-field"]').setValue('')
596
+ await nextTick()
597
+ expect(result.errors.value).toContain('Requis Vuetify input event')
598
+
599
+ wrapper.unmount()
600
+ })
601
+
602
+ it('with isValidateOnBlur=true, does not validate automatically on input change but validates on blur', async () => {
603
+ const params = {
604
+ modelValue: ref<unknown>('valeur initiale'),
605
+ readonly: ref(false),
606
+ disabled: ref(false),
607
+ required: ref(false),
608
+ isValidateOnBlur: ref(true),
609
+ showSuccessMessages: ref(true),
610
+ disableErrorHandling: ref(false),
611
+ label: ref('Mon champ'),
612
+ focused: ref(false),
613
+ useVuetifyValidation: true as const,
614
+ rules: ref([(v: unknown) => !!v || 'Requis Vuetify blur event']),
615
+ maxErrors: ref(1),
616
+ }
617
+ let result!: ReturnType<typeof useValidation>
618
+ const wrapper = mount(defineComponent({
619
+ setup() {
620
+ result = useValidation(params)
621
+ const onInput = (event: Event) => {
622
+ params.modelValue.value = (event.target as HTMLInputElement).value
623
+ }
624
+ const onFocus = () => {
625
+ params.focused.value = true
626
+ }
627
+ const onBlur = () => {
628
+ params.focused.value = false
629
+ }
630
+
631
+ return {
632
+ modelValue: params.modelValue,
633
+ onInput,
634
+ onFocus,
635
+ onBlur,
636
+ }
637
+ },
638
+ template: `<input data-test="vuetify-field" :value="modelValue" @input="onInput" @focus="onFocus" @blur="onBlur">`,
639
+ }))
640
+
641
+ await wrapper.get('[data-test="vuetify-field"]').trigger('focus')
642
+ await wrapper.get('[data-test="vuetify-field"]').setValue('')
643
+ await nextTick()
644
+ expect(result.errors.value).toEqual([])
645
+
646
+ await wrapper.get('[data-test="vuetify-field"]').trigger('blur')
647
+ await nextTick()
648
+ expect(result.errors.value).toContain('Requis Vuetify blur event')
649
+
650
+ wrapper.unmount()
651
+ })
652
+ })
653
+ })
654
+ })
655
+
656
+ describe('additional coverage', () => {
657
+ it('clears warnings and successes when validate() short-circuits on readonly', async () => {
658
+ const params = makeParams({
659
+ readonly: ref(false),
660
+ customRules: ref([{ type: 'required', options: { message: 'Requis readonly short-circuit' } }]),
661
+ modelValue: ref(''),
662
+ })
663
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
664
+
665
+ const invalid = await result.validate()
666
+ expect(invalid).toBe(false)
667
+ expect(result.errors.value).toContain('Requis readonly short-circuit')
668
+
669
+ params.readonly.value = true
670
+
671
+ const valid = await result.validate()
672
+ expect(valid).toBe(true)
673
+ expect(result.errors.value).toEqual([])
674
+ expect(result.warnings.value).toEqual([])
675
+ expect(result.successes.value).toEqual([])
676
+ })
677
+
678
+ it('hasSuccess is false when successes exist but warnings also exist', async () => {
679
+ const params = makeParams({
680
+ modelValue: ref('ok'),
681
+ hasWarningProp: ref(true),
682
+ customRules: ref([]),
683
+ customSuccessRules: ref([
684
+ {
685
+ type: 'custom',
686
+ options: {
687
+ validate: (value: unknown) => value === 'ok',
688
+ successMessage: 'ok',
689
+ },
690
+ },
691
+ ]),
692
+ })
693
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
694
+
695
+ await result.validate()
696
+ expect(result.successes.value).toContain('ok')
697
+ expect(result.hasWarning.value).toBe(true)
698
+ expect(result.hasSuccess.value).toBeFalsy()
699
+ })
700
+
701
+ it('supports reactive switch between custom and Vuetify validation modes', async () => {
702
+ const params = {
703
+ modelValue: ref<unknown>(''),
704
+ readonly: ref(false),
705
+ disabled: ref(false),
706
+ required: ref(false),
707
+ isValidateOnBlur: ref(true),
708
+ showSuccessMessages: ref(true),
709
+ disableErrorHandling: ref(false),
710
+ label: ref('Champ hybride'),
711
+ focused: ref(false),
712
+ useVuetifyValidation: ref(false),
713
+ customRules: ref([{ type: 'required', options: { message: 'Erreur custom' } }]),
714
+ customWarningRules: ref([]),
715
+ customSuccessRules: ref([]),
716
+ rules: ref([(v: unknown) => !!v || 'Erreur vuetify']),
717
+ maxErrors: ref(1),
718
+ }
719
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
720
+
721
+ let valid = await result.validate()
722
+ expect(valid).toBe(false)
723
+ expect(result.errors.value).toContain('Erreur custom')
724
+
725
+ params.useVuetifyValidation.value = true
726
+ valid = await result.validate()
727
+ expect(valid).toBe(false)
728
+ expect(result.errors.value).toContain('Erreur vuetify')
729
+ })
730
+
731
+ it('respects maxErrors in Vuetify mode', async () => {
732
+ const params = {
733
+ modelValue: ref<unknown>('x'),
734
+ readonly: ref(false),
735
+ disabled: ref(false),
736
+ required: ref(false),
737
+ isValidateOnBlur: ref(true),
738
+ showSuccessMessages: ref(true),
739
+ disableErrorHandling: ref(false),
740
+ label: ref('Mon champ'),
741
+ focused: ref(false),
742
+ useVuetifyValidation: true as const,
743
+ rules: ref([
744
+ () => 'E1',
745
+ () => 'E2',
746
+ () => 'E3',
747
+ ]),
748
+ maxErrors: ref(2),
749
+ }
750
+ const { result } = withSetup(() => useValidation(params))
751
+
752
+ const valid = await result.validate()
753
+ expect(valid).toBe(false)
754
+ expect(result.errors.value).toHaveLength(2)
755
+ expect(result.errors.value).toEqual(['E1', 'E2'])
756
+ })
757
+ })
758
+
759
+ describe('vform integration', () => {
760
+ const VForm = defineComponent({
761
+ emits: ['submit'],
762
+ template: `<form data-test="vform" @submit="$emit('submit', $event)"><slot /></form>`,
763
+ })
764
+
765
+ it('validates custom rules on VForm submit and blocks invalid state', async () => {
766
+ const params = makeParams({
767
+ useVuetifyValidation: false as const,
768
+ modelValue: ref(''),
769
+ customRules: ref([{ type: 'required', options: { message: 'Requis VForm custom' } }]),
770
+ })
771
+
772
+ let isValid = true
773
+ let result!: ReturnType<typeof useValidation>
774
+
775
+ const wrapper = mount(defineComponent({
776
+ components: { VForm },
777
+ setup() {
778
+ result = useValidation(params as Parameters<typeof useValidation>[0])
779
+ const onSubmit = async (e: Event) => {
780
+ e.preventDefault()
781
+ isValid = await result.validate()
782
+ }
783
+ const onInput = (event: Event) => {
784
+ params.modelValue.value = (event.target as HTMLInputElement).value
785
+ }
786
+
787
+ return { onSubmit, modelValue: params.modelValue, onInput }
788
+ },
789
+ template: `
790
+ <VForm @submit="onSubmit">
791
+ <input data-test="field" :value="modelValue" @input="onInput">
792
+ </VForm>
793
+ `,
794
+ }))
795
+
796
+ await wrapper.get('[data-test="vform"]').trigger('submit')
797
+ await nextTick()
798
+
799
+ expect(isValid).toBe(false)
800
+ expect(result.errors.value).toContain('Requis VForm custom')
801
+
802
+ await wrapper.get('[data-test="field"]').setValue('ok')
803
+ await wrapper.get('[data-test="vform"]').trigger('submit')
804
+ await nextTick()
805
+
806
+ expect(isValid).toBe(true)
807
+ expect(result.errors.value).toEqual([])
808
+
809
+ wrapper.unmount()
810
+ })
811
+
812
+ it('validates Vuetify rules on VForm submit', async () => {
813
+ const params = {
814
+ modelValue: ref<unknown>(''),
815
+ readonly: ref(false),
816
+ disabled: ref(false),
817
+ required: ref(false),
818
+ isValidateOnBlur: ref(true),
819
+ showSuccessMessages: ref(true),
820
+ disableErrorHandling: ref(false),
821
+ label: ref('Mon champ'),
822
+ focused: ref(false),
823
+ useVuetifyValidation: true as const,
824
+ rules: ref([(v: unknown) => !!v || 'Requis VForm Vuetify']),
825
+ maxErrors: ref(1),
826
+ }
827
+
828
+ let isValid = true
829
+ let result!: ReturnType<typeof useValidation>
830
+
831
+ const wrapper = mount(defineComponent({
832
+ components: { VForm },
833
+ setup() {
834
+ result = useValidation(params)
835
+ const onSubmit = async (e: Event) => {
836
+ e.preventDefault()
837
+ isValid = await result.validate()
838
+ }
839
+ const onInput = (event: Event) => {
840
+ params.modelValue.value = (event.target as HTMLInputElement).value
841
+ }
842
+
843
+ return { onSubmit, modelValue: params.modelValue, onInput }
844
+ },
845
+ template: `
846
+ <VForm @submit="onSubmit">
847
+ <input data-test="field" :value="modelValue" @input="onInput">
848
+ </VForm>
849
+ `,
850
+ }))
851
+
852
+ await wrapper.get('[data-test="vform"]').trigger('submit')
853
+ await nextTick()
854
+ expect(isValid).toBe(false)
855
+ expect(result.errors.value).toContain('Requis VForm Vuetify')
856
+
857
+ await wrapper.get('[data-test="field"]').setValue('ok')
858
+ await wrapper.get('[data-test="vform"]').trigger('submit')
859
+ await nextTick()
860
+ expect(isValid).toBe(true)
861
+
862
+ wrapper.unmount()
863
+ })
864
+ })
865
+
866
+ describe('syform integration', () => {
867
+ it('uses SyForm validate() with a registered validatable child', async () => {
868
+ const params = makeParams({
869
+ useVuetifyValidation: false as const,
870
+ modelValue: ref(''),
871
+ customRules: ref([{ type: 'required', options: { message: 'Requis SyForm fnc' } }]),
872
+ })
873
+
874
+ let result!: ReturnType<typeof useValidation>
875
+
876
+ const FieldUnderTest = defineComponent({
877
+ setup() {
878
+ result = useValidation(params as Parameters<typeof useValidation>[0])
879
+ const onInput = (event: Event) => {
880
+ params.modelValue.value = (event.target as HTMLInputElement).value
881
+ }
882
+
883
+ return { modelValue: params.modelValue, onInput }
884
+ },
885
+ template: `<input data-test="field" :value="modelValue" @input="onInput">`,
886
+ })
887
+
888
+ const wrapper = mount(defineComponent({
889
+ components: { SyForm, FieldUnderTest },
890
+ template: `
891
+ <SyForm data-test="syform">
892
+ <FieldUnderTest />
893
+ </SyForm>
894
+ `,
895
+ }))
896
+
897
+ await nextTick()
898
+ expect(result.errors.value).toEqual([])
899
+
900
+ const syFormVm = wrapper.getComponent(SyForm).vm as {
901
+ validate: () => Promise<boolean>
902
+ clearValidation: () => void
903
+ }
904
+
905
+ const invalid = await syFormVm.validate()
906
+ expect(invalid).toBe(false)
907
+ expect(result.errors.value).toContain('Requis SyForm fnc')
908
+
909
+ await wrapper.get('[data-test="field"]').setValue('ok')
910
+ const valid = await syFormVm.validate()
911
+ expect(valid).toBe(true)
912
+
913
+ wrapper.unmount()
914
+ })
915
+
916
+ it('uses SyForm clearValidation() with a registered validatable child', async () => {
917
+ const params = makeParams({
918
+ useVuetifyValidation: false as const,
919
+ modelValue: ref(''),
920
+ customRules: ref([{ type: 'required', options: { message: 'Requis SyForm clear' } }]),
921
+ })
922
+
923
+ let result!: ReturnType<typeof useValidation>
924
+
925
+ const FieldUnderTest = defineComponent({
926
+ setup() {
927
+ result = useValidation(params as Parameters<typeof useValidation>[0])
928
+ const onInput = (event: Event) => {
929
+ params.modelValue.value = (event.target as HTMLInputElement).value
930
+ }
931
+
932
+ return { modelValue: params.modelValue, onInput }
933
+ },
934
+ template: `<input data-test="field" :value="modelValue" @input="onInput">`,
935
+ })
936
+
937
+ const wrapper = mount(defineComponent({
938
+ components: { SyForm, FieldUnderTest },
939
+ template: `
940
+ <SyForm data-test="syform">
941
+ <FieldUnderTest />
942
+ </SyForm>
943
+ `,
944
+ }))
945
+
946
+ await nextTick()
947
+
948
+ const syFormVm = wrapper.getComponent(SyForm).vm as {
949
+ validate: () => Promise<boolean>
950
+ clearValidation: () => void
951
+ }
952
+
953
+ await syFormVm.validate()
954
+ expect(result.errors.value).toContain('Requis SyForm clear')
955
+
956
+ syFormVm.clearValidation()
957
+ await nextTick()
958
+ expect(result.errors.value).toEqual([])
959
+
960
+ wrapper.unmount()
961
+ })
962
+
963
+ it('uses SyForm reset() with a registered validatable child — clears errors, resets modelValue and emits reset', async () => {
964
+ const params = makeParams({
965
+ useVuetifyValidation: false as const,
966
+ modelValue: ref(''),
967
+ customRules: ref([{ type: 'required', options: { message: 'Requis SyForm reset' } }]),
968
+ })
969
+
970
+ let result!: ReturnType<typeof useValidation>
971
+
972
+ const FieldUnderTest = defineComponent({
973
+ setup() {
974
+ result = useValidation(params as Parameters<typeof useValidation>[0])
975
+ const onInput = (event: Event) => {
976
+ params.modelValue.value = (event.target as HTMLInputElement).value
977
+ }
978
+
979
+ return { modelValue: params.modelValue, onInput }
980
+ },
981
+ template: `<input data-test="field" :value="modelValue" @input="onInput">`,
982
+ })
983
+
984
+ const wrapper = mount(defineComponent({
985
+ components: { SyForm, FieldUnderTest },
986
+ setup() {
987
+ const onReset = () => {
988
+ params.modelValue.value = ''
989
+ }
990
+
991
+ return { onReset }
992
+ },
993
+ template: `
994
+ <SyForm data-test="syform" @reset="onReset">
995
+ <FieldUnderTest />
996
+ </SyForm>
997
+ `,
998
+ }))
999
+
1000
+ await nextTick()
1001
+
1002
+ const syFormVm = wrapper.getComponent(SyForm).vm as {
1003
+ validate: () => Promise<boolean>
1004
+ reset: () => void
1005
+ }
1006
+
1007
+ // Fill the field then validate so errors are empty
1008
+ await wrapper.get('[data-test="field"]').setValue('filled')
1009
+ await syFormVm.validate()
1010
+ expect(result.errors.value).toEqual([])
1011
+ expect(params.modelValue.value).toBe('filled')
1012
+
1013
+ // Reset: clears errors, resets modelValue via @reset handler, emits reset
1014
+ syFormVm.reset()
1015
+ await nextTick()
1016
+
1017
+ expect(result.errors.value).toEqual([])
1018
+ expect(params.modelValue.value).toBe('')
1019
+ expect(wrapper.getComponent(SyForm).emitted('reset')).toHaveLength(1)
1020
+
1021
+ wrapper.unmount()
1022
+ })
1023
+
1024
+ it('uses SyForm reset() with useVuetifyValidation — clears errors, resets modelValue and emits reset', async () => {
1025
+ const params = {
1026
+ modelValue: ref<unknown>(''),
1027
+ readonly: ref(false),
1028
+ disabled: ref(false),
1029
+ required: ref(false),
1030
+ isValidateOnBlur: ref(true),
1031
+ showSuccessMessages: ref(true),
1032
+ disableErrorHandling: ref(false),
1033
+ label: ref('Mon champ'),
1034
+ focused: ref(false),
1035
+ useVuetifyValidation: true as const,
1036
+ rules: ref([(v: unknown) => !!v || 'Requis SyForm Vuetify reset']),
1037
+ maxErrors: ref(1),
1038
+ }
1039
+
1040
+ let result!: ReturnType<typeof useValidation>
1041
+
1042
+ const FieldUnderTest = defineComponent({
1043
+ setup() {
1044
+ result = useValidation(params)
1045
+ const onInput = (event: Event) => {
1046
+ params.modelValue.value = (event.target as HTMLInputElement).value
1047
+ }
1048
+
1049
+ return { modelValue: params.modelValue, onInput }
1050
+ },
1051
+ template: `<input data-test="field" :value="modelValue" @input="onInput">`,
1052
+ })
1053
+
1054
+ const wrapper = mount(defineComponent({
1055
+ components: { SyForm, FieldUnderTest },
1056
+ setup() {
1057
+ const onReset = () => {
1058
+ params.modelValue.value = ''
1059
+ }
1060
+
1061
+ return { onReset }
1062
+ },
1063
+ template: `
1064
+ <SyForm data-test="syform" @reset="onReset">
1065
+ <FieldUnderTest />
1066
+ </SyForm>
1067
+ `,
1068
+ }))
1069
+
1070
+ await nextTick()
1071
+
1072
+ const syFormVm = wrapper.getComponent(SyForm).vm as {
1073
+ validate: () => Promise<boolean>
1074
+ reset: () => void
1075
+ }
1076
+
1077
+ // Fill the field then validate so errors are empty
1078
+ await wrapper.get('[data-test="field"]').setValue('filled')
1079
+ await syFormVm.validate()
1080
+ expect(result.errors.value).toEqual([])
1081
+ expect(params.modelValue.value).toBe('filled')
1082
+
1083
+ // Reset: clears errors, resets modelValue via @reset handler, emits reset
1084
+ syFormVm.reset()
1085
+ await nextTick()
1086
+
1087
+ expect(result.errors.value).toEqual([])
1088
+ expect(params.modelValue.value).toBe('')
1089
+ expect(wrapper.getComponent(SyForm).emitted('reset')).toHaveLength(1)
1090
+
1091
+ wrapper.unmount()
1092
+ })
1093
+
1094
+ it('validates custom rules when SyForm emits submit', async () => {
1095
+ const params = makeParams({
1096
+ useVuetifyValidation: false as const,
1097
+ modelValue: ref(''),
1098
+ customRules: ref([{ type: 'required', options: { message: 'Requis SyForm custom' } }]),
1099
+ })
1100
+
1101
+ let isValid = true
1102
+ let result!: ReturnType<typeof useValidation>
1103
+
1104
+ const wrapper = mount(defineComponent({
1105
+ components: { SyForm },
1106
+ setup() {
1107
+ result = useValidation(params as Parameters<typeof useValidation>[0])
1108
+ const onSubmit = async () => {
1109
+ isValid = await result.validate()
1110
+ }
1111
+ const onInput = (event: Event) => {
1112
+ params.modelValue.value = (event.target as HTMLInputElement).value
1113
+ }
1114
+
1115
+ return { onSubmit, modelValue: params.modelValue, onInput }
1116
+ },
1117
+ template: `
1118
+ <SyForm data-test="syform" :validate-on-submit="false" @submit="onSubmit">
1119
+ <input data-test="field" :value="modelValue" @input="onInput">
1120
+ </SyForm>
1121
+ `,
1122
+ }))
1123
+
1124
+ await wrapper.get('[data-test="syform"]').trigger('submit')
1125
+ await nextTick()
1126
+
1127
+ expect(isValid).toBe(false)
1128
+ expect(result.errors.value).toContain('Requis SyForm custom')
1129
+
1130
+ await wrapper.get('[data-test="field"]').setValue('ok')
1131
+ await wrapper.get('[data-test="syform"]').trigger('submit')
1132
+ await nextTick()
1133
+
1134
+ expect(isValid).toBe(true)
1135
+ expect(result.errors.value).toEqual([])
1136
+
1137
+ wrapper.unmount()
1138
+ })
1139
+
1140
+ it('validates Vuetify rules when SyForm emits submit', async () => {
1141
+ const params = {
1142
+ modelValue: ref<unknown>(''),
1143
+ readonly: ref(false),
1144
+ disabled: ref(false),
1145
+ required: ref(false),
1146
+ isValidateOnBlur: ref(true),
1147
+ showSuccessMessages: ref(true),
1148
+ disableErrorHandling: ref(false),
1149
+ label: ref('Mon champ'),
1150
+ focused: ref(false),
1151
+ useVuetifyValidation: true as const,
1152
+ rules: ref([(v: unknown) => !!v || 'Requis SyForm Vuetify']),
1153
+ maxErrors: ref(1),
1154
+ }
1155
+
1156
+ let isValid = true
1157
+ let result!: ReturnType<typeof useValidation>
1158
+
1159
+ const wrapper = mount(defineComponent({
1160
+ components: { SyForm },
1161
+ setup() {
1162
+ result = useValidation(params)
1163
+ const onSubmit = async () => {
1164
+ isValid = await result.validate()
1165
+ }
1166
+ const onInput = (event: Event) => {
1167
+ params.modelValue.value = (event.target as HTMLInputElement).value
1168
+ }
1169
+
1170
+ return { onSubmit, modelValue: params.modelValue, onInput }
1171
+ },
1172
+ template: `
1173
+ <SyForm data-test="syform" :validate-on-submit="false" @submit="onSubmit">
1174
+ <input data-test="field" :value="modelValue" @input="onInput">
1175
+ </SyForm>
1176
+ `,
1177
+ }))
1178
+
1179
+ await wrapper.get('[data-test="syform"]').trigger('submit')
1180
+ await nextTick()
1181
+ expect(isValid).toBe(false)
1182
+ expect(result.errors.value).toContain('Requis SyForm Vuetify')
1183
+
1184
+ await wrapper.get('[data-test="field"]').setValue('ok')
1185
+ await wrapper.get('[data-test="syform"]').trigger('submit')
1186
+ await nextTick()
1187
+ expect(isValid).toBe(true)
1188
+
1189
+ wrapper.unmount()
1190
+ })
1191
+
1192
+ it('emits reset and allows parent side effects from reset handler', async () => {
1193
+ const params = makeParams({
1194
+ useVuetifyValidation: false as const,
1195
+ modelValue: ref(''),
1196
+ customRules: ref([{ type: 'required', options: { message: 'Requis SyForm reset' } }]),
1197
+ })
1198
+
1199
+ let result!: ReturnType<typeof useValidation>
1200
+ const resetCount = ref(0)
1201
+
1202
+ const wrapper = mount(defineComponent({
1203
+ components: { SyForm },
1204
+ setup() {
1205
+ result = useValidation(params as Parameters<typeof useValidation>[0])
1206
+ const onSubmit = async () => {
1207
+ await result.validate()
1208
+ }
1209
+ const onInput = (event: Event) => {
1210
+ params.modelValue.value = (event.target as HTMLInputElement).value
1211
+ }
1212
+ const onReset = () => {
1213
+ resetCount.value += 1
1214
+ params.modelValue.value = ''
1215
+ }
1216
+
1217
+ return { onSubmit, onReset, modelValue: params.modelValue, onInput }
1218
+ },
1219
+ template: `
1220
+ <SyForm data-test="syform" :validate-on-submit="false" @submit="onSubmit" @reset="onReset">
1221
+ <input data-test="field" :value="modelValue" @input="onInput">
1222
+ </SyForm>
1223
+ `,
1224
+ }))
1225
+
1226
+ await wrapper.get('[data-test="syform"]').trigger('submit')
1227
+ await nextTick()
1228
+ expect(result.errors.value).toContain('Requis SyForm reset')
1229
+
1230
+ await wrapper.get('[data-test="syform"]').trigger('reset')
1231
+ await nextTick()
1232
+
1233
+ expect(resetCount.value).toBe(1)
1234
+ expect(params.modelValue.value).toBe('')
1235
+ expect(result.errors.value).toContain('Requis SyForm reset')
1236
+
1237
+ wrapper.unmount()
1238
+ })
1239
+ })
1240
+
1241
+ describe('computed hasError / hasWarning / hasSuccess', () => {
1242
+ it('hasError is true when errors array is non-empty', async () => {
1243
+ const params = makeParams({
1244
+ modelValue: ref(''),
1245
+ customRules: ref([{ type: 'required', options: { message: 'Une erreur' } }]),
1246
+ })
1247
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1248
+
1249
+ await result.validate()
1250
+ expect(result.errors.value).toContain('Une erreur')
1251
+ expect(result.hasError.value).toBe(true)
1252
+ })
1253
+
1254
+ it('hasError is true when hasErrorProp is true', async () => {
1255
+ const params = makeParams({ hasErrorProp: ref(true) })
1256
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1257
+
1258
+ expect(result.hasError.value).toBe(true)
1259
+ })
1260
+
1261
+ it('hasWarning is true when warnings array is non-empty', async () => {
1262
+ const params = makeParams({
1263
+ modelValue: ref('ko'),
1264
+ customRules: ref([]),
1265
+ customWarningRules: ref([
1266
+ {
1267
+ type: 'custom',
1268
+ options: {
1269
+ validate: (value: unknown) => value === 'ok',
1270
+ warningMessage: 'Un avertissement',
1271
+ isWarning: true,
1272
+ },
1273
+ },
1274
+ ]),
1275
+ })
1276
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1277
+
1278
+ await result.validate()
1279
+ expect(result.warnings.value).toContain('Un avertissement')
1280
+ expect(result.hasWarning.value).toBe(true)
1281
+ })
1282
+
1283
+ it('hasWarning is true when hasWarningProp is true', async () => {
1284
+ const params = makeParams({ hasWarningProp: ref(true) })
1285
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1286
+
1287
+ expect(result.hasWarning.value).toBe(true)
1288
+ })
1289
+
1290
+ it('hasSuccess is true when successes are non-empty and no errors or warnings', async () => {
1291
+ const params = makeParams({
1292
+ modelValue: ref('ok'),
1293
+ customRules: ref([]),
1294
+ customSuccessRules: ref([
1295
+ {
1296
+ type: 'custom',
1297
+ options: {
1298
+ validate: (value: unknown) => value === 'ok',
1299
+ successMessage: 'Succès',
1300
+ },
1301
+ },
1302
+ ]),
1303
+ })
1304
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1305
+
1306
+ await result.validate()
1307
+ expect(result.successes.value).toContain('Succès')
1308
+ expect(result.hasSuccess.value).toBe(true)
1309
+ })
1310
+
1311
+ it('hasSuccess is falsy when there are both successes and errors', async () => {
1312
+ const params = makeParams({
1313
+ modelValue: ref('ok'),
1314
+ hasErrorProp: ref(true),
1315
+ customRules: ref([]),
1316
+ customSuccessRules: ref([
1317
+ {
1318
+ type: 'custom',
1319
+ options: {
1320
+ validate: (value: unknown) => value === 'ok',
1321
+ successMessage: 'Succès',
1322
+ },
1323
+ },
1324
+ ]),
1325
+ })
1326
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1327
+
1328
+ await result.validate()
1329
+ expect(result.successes.value).toContain('Succès')
1330
+ expect(result.hasError.value).toBe(true)
1331
+ expect(result.hasSuccess.value).toBeFalsy()
1332
+ })
1333
+
1334
+ it('hasSuccess is true when hasSuccessProp is true and no errors/warnings', async () => {
1335
+ const params = makeParams({ hasSuccessProp: ref(true) })
1336
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1337
+
1338
+ expect(result.hasSuccess.value).toBe(true)
1339
+ })
1340
+ })
1341
+
1342
+ describe('auto-revalidation on config change', () => {
1343
+ it('auto-revalidates when customRules change and field is dirty', async () => {
1344
+ const params = makeParams({
1345
+ modelValue: ref('hello'),
1346
+ customRules: ref<ValidationRule[]>([{ type: 'required', options: { message: 'Requis' } }]),
1347
+ })
1348
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1349
+
1350
+ // Make the field dirty
1351
+ await result.validate()
1352
+ expect(result.errors.value).toEqual([])
1353
+
1354
+ // Change rules to one the value doesn't satisfy
1355
+ params.customRules.value = [{
1356
+ type: 'minLength',
1357
+ options: { length: 20, message: 'Trop court' },
1358
+ }]
1359
+ await nextTick()
1360
+
1361
+ // Should auto-validate since the field was dirty
1362
+ expect(result.errors.value.length).toBeGreaterThan(0)
1363
+ })
1364
+
1365
+ it('auto-revalidates when disableErrorHandling changes and field is dirty', async () => {
1366
+ const params = makeParams({
1367
+ modelValue: ref(''),
1368
+ customRules: ref([{ type: 'required', options: { message: 'Requis' } }]),
1369
+ })
1370
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1371
+
1372
+ await result.validate()
1373
+ expect(result.errors.value).toContain('Requis')
1374
+
1375
+ params.disableErrorHandling.value = true
1376
+ await nextTick()
1377
+
1378
+ // Should auto-clear errors
1379
+ expect(result.errors.value).toEqual([])
1380
+ })
1381
+
1382
+ it('auto-revalidates when showSuccessMessages changes and field is dirty', async () => {
1383
+ const params = makeParams({
1384
+ modelValue: ref('valid'),
1385
+ customRules: ref([]),
1386
+ })
1387
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1388
+
1389
+ await result.validate()
1390
+ expect(result.successes.value.length).toBeGreaterThan(0)
1391
+
1392
+ params.showSuccessMessages.value = false
1393
+ await nextTick()
1394
+
1395
+ expect(result.successes.value).toEqual([])
1396
+ })
1397
+
1398
+ it('does not auto-revalidate when field is not dirty', async () => {
1399
+ const params = makeParams({
1400
+ modelValue: ref(''),
1401
+ customRules: ref([{ type: 'required', options: { message: 'Requis' } }]),
1402
+ })
1403
+ withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1404
+
1405
+ // Don't validate — field is pristine
1406
+ expect(result => result).toBeDefined()
1407
+
1408
+ params.customRules.value = [{
1409
+ type: 'minLength',
1410
+ options: { length: 20, message: 'Trop court' },
1411
+ }]
1412
+ await nextTick()
1413
+
1414
+ // Should NOT auto-validate
1415
+ expect(params.modelValue.value).toBe('')
1416
+ })
1417
+
1418
+ it('auto-revalidates when label changes and field is dirty', async () => {
1419
+ const params = makeParams({
1420
+ modelValue: ref('valid'),
1421
+ customRules: ref([]),
1422
+ })
1423
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1424
+
1425
+ await result.validate()
1426
+ expect(result.successes.value.some((s: string) => s.includes('Mon champ'))).toBe(true)
1427
+
1428
+ params.label.value = 'Nouveau label'
1429
+ await nextTick()
1430
+
1431
+ expect(result.successes.value.some((s: string) => s.includes('Nouveau label'))).toBe(true)
1432
+ })
1433
+ })
1434
+
1435
+ describe('deduplication and message merging', () => {
1436
+ it('deduplicates errors when same message comes from rules and errorMessages', async () => {
1437
+ const params = makeParams({
1438
+ modelValue: ref(''),
1439
+ customRules: ref([{ type: 'required', options: { message: 'Requis' } }]),
1440
+ errorMessages: ref<string[] | null>(['Requis']),
1441
+ })
1442
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1443
+
1444
+ await result.validate()
1445
+ const requis = result.errors.value.filter((e: string) => e === 'Requis')
1446
+ expect(requis).toHaveLength(1)
1447
+ })
1448
+
1449
+ it('merges distinct errors from rules and errorMessages', async () => {
1450
+ const params = makeParams({
1451
+ modelValue: ref(''),
1452
+ customRules: ref([{ type: 'required', options: { message: 'Champ requis' } }]),
1453
+ errorMessages: ref<string[] | null>(['Erreur externe']),
1454
+ })
1455
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1456
+
1457
+ await result.validate()
1458
+ expect(result.errors.value).toContain('Champ requis')
1459
+ expect(result.errors.value).toContain('Erreur externe')
1460
+ })
1461
+
1462
+ it('merges distinct warnings from rules and warningMessages', async () => {
1463
+ const params = makeParams({
1464
+ modelValue: ref('ko'),
1465
+ customRules: ref([]),
1466
+ customWarningRules: ref([{
1467
+ type: 'custom',
1468
+ options: {
1469
+ validate: (value: unknown) => value === 'ok',
1470
+ warningMessage: 'Warning interne',
1471
+ isWarning: true,
1472
+ },
1473
+ }]),
1474
+ warningMessages: ref<string[] | null>(['Warning externe']),
1475
+ })
1476
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1477
+
1478
+ await result.validate()
1479
+ expect(result.warnings.value).toContain('Warning interne')
1480
+ expect(result.warnings.value).toContain('Warning externe')
1481
+ })
1482
+
1483
+ it('keeps external successMessages even when showSuccessMessages is false', async () => {
1484
+ const params = makeParams({
1485
+ modelValue: ref('valid'),
1486
+ showSuccessMessages: ref(false),
1487
+ customRules: ref([]),
1488
+ successMessages: ref<string[] | null>(['Succès externe']),
1489
+ })
1490
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1491
+
1492
+ await result.validate()
1493
+ expect(result.successes.value).toContain('Succès externe')
1494
+ })
1495
+
1496
+ it('hides inner successes but keeps external successMessages when showSuccessMessages is false', async () => {
1497
+ const params = makeParams({
1498
+ modelValue: ref('ok'),
1499
+ showSuccessMessages: ref(false),
1500
+ customRules: ref([]),
1501
+ customSuccessRules: ref([{
1502
+ type: 'custom',
1503
+ options: {
1504
+ validate: (value: unknown) => value === 'ok',
1505
+ successMessage: 'Succès interne',
1506
+ },
1507
+ }]),
1508
+ successMessages: ref<string[] | null>(['Succès externe']),
1509
+ })
1510
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1511
+
1512
+ await result.validate()
1513
+ expect(result.successes.value).not.toContain('Succès interne')
1514
+ expect(result.successes.value).toContain('Succès externe')
1515
+ })
1516
+ })
1517
+
1518
+ describe('rules concurrences', () => {
1519
+ it('last validate() call wins when multiple calls are made rapidly', async () => {
1520
+ let resolveFirst!: (v: boolean) => void
1521
+ let resolveSecond!: (v: boolean) => void
1522
+
1523
+ const firstRule = {
1524
+ type: 'custom',
1525
+ options: {
1526
+ validate: () => new Promise<boolean>((resolve) => { resolveFirst = resolve }),
1527
+ message: 'Erreur première',
1528
+ },
1529
+ }
1530
+ const secondRule = {
1531
+ type: 'custom',
1532
+ options: {
1533
+ validate: () => new Promise<boolean>((resolve) => { resolveSecond = resolve }),
1534
+ message: 'Erreur seconde',
1535
+ },
1536
+ }
1537
+
1538
+ const customRules = ref<ValidationRule[]>([firstRule])
1539
+ const params = makeParams({
1540
+ modelValue: ref('test'),
1541
+ customRules,
1542
+ })
1543
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1544
+
1545
+ // First validation call (with slow-resolving rule)
1546
+ const firstPromise = result.validate()
1547
+
1548
+ // Swap to a different rule and trigger second validation before first resolves
1549
+ customRules.value = [secondRule]
1550
+ const secondPromise = result.validate()
1551
+
1552
+ // Resolve second first with failure (it should be the one that matters)
1553
+ resolveSecond(false)
1554
+ await secondPromise
1555
+
1556
+ expect(result.errors.value).toContain('Erreur seconde')
1557
+
1558
+ // Now resolve first with failure (stale — should be discarded)
1559
+ resolveFirst(false)
1560
+ await firstPromise
1561
+
1562
+ // The stale first result should NOT overwrite the second
1563
+ expect(result.errors.value).toContain('Erreur seconde')
1564
+ expect(result.errors.value).not.toContain('Erreur première')
1565
+ })
1566
+
1567
+ it('concurrent validate() calls with different model values keep only the latest result', async () => {
1568
+ const resolvers: Array<(v: boolean) => void> = []
1569
+ let callIndex = 0
1570
+
1571
+ const params = makeParams({
1572
+ modelValue: ref<unknown>('a'),
1573
+ customRules: ref<ValidationRule[]>([{
1574
+ type: 'custom',
1575
+ options: {
1576
+ validate: () => {
1577
+ const idx = callIndex++
1578
+ return new Promise<boolean>((resolve) => {
1579
+ resolvers[idx] = resolve
1580
+ })
1581
+ },
1582
+ message: 'Erreur validation',
1583
+ },
1584
+ }]),
1585
+ })
1586
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1587
+
1588
+ // Trigger first validation with modelValue 'a'
1589
+ const p1 = result.validate()
1590
+
1591
+ // Change model value and trigger second validation
1592
+ params.modelValue.value = 'b'
1593
+ const p2 = result.validate()
1594
+
1595
+ // Change model value again and trigger third validation
1596
+ params.modelValue.value = 'c'
1597
+ const p3 = result.validate()
1598
+
1599
+ // Resolve out of order: third fails, first fails, second fails
1600
+ resolvers[2]!(false)
1601
+ resolvers[0]!(false)
1602
+ resolvers[1]!(false)
1603
+
1604
+ await Promise.all([p1, p2, p3])
1605
+
1606
+ // Only the last validation (third) should populate errors
1607
+ expect(result.errors.value).toContain('Erreur validation')
1608
+ // Stale results should have been discarded (only 1 error from the latest call)
1609
+ expect(result.errors.value).toHaveLength(1)
1610
+ })
1611
+
1612
+ it('multiple async error rules on the same validation collect all errors', async () => {
1613
+ const params = makeParams({
1614
+ modelValue: ref('bad'),
1615
+ customRules: ref<ValidationRule[]>([
1616
+ {
1617
+ type: 'custom',
1618
+ options: {
1619
+ validate: async () => false,
1620
+ message: 'Erreur async 1',
1621
+ },
1622
+ },
1623
+ {
1624
+ type: 'custom',
1625
+ options: {
1626
+ validate: async () => false,
1627
+ message: 'Erreur async 2',
1628
+ },
1629
+ },
1630
+ {
1631
+ type: 'custom',
1632
+ options: {
1633
+ validate: async () => false,
1634
+ message: 'Erreur async 3',
1635
+ },
1636
+ },
1637
+ ]),
1638
+ })
1639
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1640
+
1641
+ const valid = await result.validate()
1642
+ expect(valid).toBe(false)
1643
+ expect(result.errors.value).toContain('Erreur async 1')
1644
+ expect(result.errors.value).toContain('Erreur async 2')
1645
+ expect(result.errors.value).toContain('Erreur async 3')
1646
+ })
1647
+
1648
+ it('concurrent error, warning, and success rules resolve without interference', async () => {
1649
+ const params = makeParams({
1650
+ modelValue: ref('ok'),
1651
+ customRules: ref<ValidationRule[]>([
1652
+ {
1653
+ type: 'custom',
1654
+ options: {
1655
+ validate: async (v: unknown) => v === 'ok',
1656
+ message: 'Erreur concurrente',
1657
+ },
1658
+ },
1659
+ ]),
1660
+ customWarningRules: ref<ValidationRule[]>([
1661
+ {
1662
+ type: 'custom',
1663
+ options: {
1664
+ validate: async () => false,
1665
+ warningMessage: 'Warning concurrent',
1666
+ isWarning: true,
1667
+ },
1668
+ },
1669
+ ]),
1670
+ customSuccessRules: ref<ValidationRule[]>([
1671
+ {
1672
+ type: 'custom',
1673
+ options: {
1674
+ validate: async (v: unknown) => v === 'ok',
1675
+ successMessage: 'Succès concurrent',
1676
+ },
1677
+ },
1678
+ ]),
1679
+ })
1680
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1681
+
1682
+ const valid = await result.validate()
1683
+
1684
+ // No error since value is 'ok'
1685
+ expect(valid).toBe(true)
1686
+ expect(result.errors.value).toEqual([])
1687
+
1688
+ // Warnings are populated independently
1689
+ expect(result.warnings.value).toContain('Warning concurrent')
1690
+
1691
+ // Success is suppressed because warnings exist
1692
+ expect(result.hasSuccess.value).toBeFalsy()
1693
+ })
1694
+
1695
+ it('mixed sync and async rules on the same field work correctly together', async () => {
1696
+ const params = makeParams({
1697
+ modelValue: ref('ab'),
1698
+ customRules: ref<ValidationRule[]>([
1699
+ {
1700
+ type: 'minLength',
1701
+ options: { length: 5, message: 'Trop court sync' },
1702
+ },
1703
+ {
1704
+ type: 'custom',
1705
+ options: {
1706
+ validate: async (v: unknown) => String(v).length >= 10,
1707
+ message: 'Trop court async',
1708
+ },
1709
+ },
1710
+ {
1711
+ type: 'custom',
1712
+ options: {
1713
+ validate: async (v: unknown) => String(v).length >= 1,
1714
+ message: 'Pas d\'erreur async',
1715
+ },
1716
+ },
1717
+ ]),
1718
+ })
1719
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1720
+
1721
+ const valid = await result.validate()
1722
+ expect(valid).toBe(false)
1723
+ expect(result.errors.value).toContain('Trop court sync')
1724
+ expect(result.errors.value).toContain('Trop court async')
1725
+ })
1726
+
1727
+ it('rapid re-validations on input change discard intermediate stale results', async () => {
1728
+ const resolverQueue: Array<(v: boolean) => void> = []
1729
+
1730
+ const params = makeParams({
1731
+ modelValue: ref<unknown>(''),
1732
+ isValidateOnBlur: ref(false),
1733
+ customRules: ref<ValidationRule[]>([{
1734
+ type: 'custom',
1735
+ options: {
1736
+ validate: () => new Promise<boolean>((resolve) => { resolverQueue.push(resolve) }),
1737
+ message: 'Erreur stale',
1738
+ },
1739
+ }]),
1740
+ })
1741
+
1742
+ let result!: ReturnType<typeof useValidation>
1743
+ const wrapper = mount(defineComponent({
1744
+ setup() {
1745
+ result = useValidation(params as Parameters<typeof useValidation>[0])
1746
+ const onInput = (event: Event) => {
1747
+ params.modelValue.value = (event.target as HTMLInputElement).value
1748
+ }
1749
+
1750
+ return { modelValue: params.modelValue, onInput }
1751
+ },
1752
+ template: `<input data-test="field" :value="modelValue" @input="onInput">`,
1753
+ }))
1754
+
1755
+ // Rapid sequential input changes
1756
+ await wrapper.get('[data-test="field"]').setValue('a')
1757
+ await nextTick()
1758
+ await wrapper.get('[data-test="field"]').setValue('ab')
1759
+ await nextTick()
1760
+ await wrapper.get('[data-test="field"]').setValue('abc')
1761
+ await nextTick()
1762
+
1763
+ // Resolve all pending validators
1764
+ resolverQueue.forEach((resolve, i) => {
1765
+ if (i < resolverQueue.length - 1) {
1766
+ resolve(false) // Stale intermediate results fail
1767
+ }
1768
+ else {
1769
+ resolve(true) // Last one passes
1770
+ }
1771
+ })
1772
+
1773
+ await nextTick()
1774
+ await nextTick()
1775
+
1776
+ // Only the latest validation result should matter
1777
+ expect(result.errors.value).toEqual([])
1778
+
1779
+ wrapper.unmount()
1780
+ })
1781
+
1782
+ it('validate() called while previous async validation is pending replaces its results', async () => {
1783
+ let slowResolve!: (v: boolean) => void
1784
+
1785
+ const params = makeParams({
1786
+ modelValue: ref('test'),
1787
+ customRules: ref<ValidationRule[]>([{
1788
+ type: 'custom',
1789
+ options: {
1790
+ validate: () => new Promise<boolean>((resolve) => { slowResolve = resolve }),
1791
+ message: 'Erreur lente obsolète',
1792
+ },
1793
+ }]),
1794
+ })
1795
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1796
+
1797
+ // Start slow async validation
1798
+ const slowPromise = result.validate()
1799
+
1800
+ // Immediately swap to a sync rule and re-validate
1801
+ params.customRules.value = [{
1802
+ type: 'custom',
1803
+ options: {
1804
+ validate: () => false,
1805
+ message: 'Erreur sync immédiate',
1806
+ },
1807
+ }]
1808
+ const fastResult = await result.validate()
1809
+
1810
+ expect(fastResult).toBe(false)
1811
+ expect(result.errors.value).toContain('Erreur sync immédiate')
1812
+
1813
+ // Resolve slow async — should be discarded
1814
+ slowResolve(false)
1815
+ await slowPromise
1816
+
1817
+ expect(result.errors.value).toContain('Erreur sync immédiate')
1818
+ expect(result.errors.value).not.toContain('Erreur lente obsolète')
1819
+ })
1820
+
1821
+ it('concurrent Vuetify validations with rapid calls keep only the latest model state', async () => {
1822
+ const params = {
1823
+ modelValue: ref<unknown>(''),
1824
+ readonly: ref(false),
1825
+ disabled: ref(false),
1826
+ required: ref(false),
1827
+ isValidateOnBlur: ref(true),
1828
+ showSuccessMessages: ref(true),
1829
+ disableErrorHandling: ref(false),
1830
+ label: ref('Mon champ'),
1831
+ focused: ref(false),
1832
+ useVuetifyValidation: true as const,
1833
+ rules: ref([(v: unknown) => !!v || 'Requis Vuetify concurrent']),
1834
+ maxErrors: ref(1),
1835
+ }
1836
+ const { result } = withSetup(() => useValidation(params))
1837
+
1838
+ // First call — modelValue is empty, should fail
1839
+ const p1 = result.validate()
1840
+ await p1
1841
+ expect(result.errors.value).toContain('Requis Vuetify concurrent')
1842
+
1843
+ // Update model value and validate again
1844
+ params.modelValue.value = 'ok'
1845
+ const p2 = result.validate()
1846
+ await p2
1847
+
1848
+ // Latest result should reflect the valid state
1849
+ expect(result.errors.value).toEqual([])
1850
+ expect(result.hasError.value).toBeFalsy()
1851
+ })
1852
+
1853
+ it('async error rules with varying delays all contribute to the same validation', async () => {
1854
+ const params = makeParams({
1855
+ modelValue: ref('bad'),
1856
+ customRules: ref<ValidationRule[]>([
1857
+ {
1858
+ type: 'custom',
1859
+ options: {
1860
+ validate: () => new Promise<boolean>(resolve => setTimeout(() => resolve(false), 10)),
1861
+ message: 'Erreur rapide',
1862
+ },
1863
+ },
1864
+ {
1865
+ type: 'custom',
1866
+ options: {
1867
+ validate: () => new Promise<boolean>(resolve => setTimeout(() => resolve(false), 50)),
1868
+ message: 'Erreur lente',
1869
+ },
1870
+ },
1871
+ ]),
1872
+ })
1873
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1874
+
1875
+ const valid = await result.validate()
1876
+ expect(valid).toBe(false)
1877
+ expect(result.errors.value).toContain('Erreur rapide')
1878
+ expect(result.errors.value).toContain('Erreur lente')
1879
+ })
1880
+
1881
+ it('concurrent async warning and success rules resolve correctly when no errors', async () => {
1882
+ const params = makeParams({
1883
+ modelValue: ref('ok'),
1884
+ customRules: ref<ValidationRule[]>([]),
1885
+ customWarningRules: ref<ValidationRule[]>([
1886
+ {
1887
+ type: 'custom',
1888
+ options: {
1889
+ validate: async (v: unknown) => v === 'perfect',
1890
+ warningMessage: 'Attention',
1891
+ isWarning: true,
1892
+ },
1893
+ },
1894
+ ]),
1895
+ customSuccessRules: ref<ValidationRule[]>([
1896
+ {
1897
+ type: 'custom',
1898
+ options: {
1899
+ validate: async (v: unknown) => v === 'ok',
1900
+ successMessage: 'Bravo',
1901
+ },
1902
+ },
1903
+ ]),
1904
+ })
1905
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1906
+
1907
+ await result.validate()
1908
+
1909
+ expect(result.errors.value).toEqual([])
1910
+ expect(result.warnings.value).toContain('Attention')
1911
+ // Success suppressed by warning presence
1912
+ expect(result.hasSuccess.value).toBeFalsy()
1913
+ })
1914
+
1915
+ it('validate() returns true immediately when readonly is toggled, even with a pending async rule', async () => {
1916
+ let resolve!: (v: boolean) => void
1917
+
1918
+ const params = makeParams({
1919
+ modelValue: ref('some value'),
1920
+ customRules: ref<ValidationRule[]>([{
1921
+ type: 'custom',
1922
+ options: {
1923
+ validate: () => new Promise<boolean>((r) => { resolve = r }),
1924
+ message: 'Erreur obsolète readonly',
1925
+ },
1926
+ }]),
1927
+ })
1928
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1929
+
1930
+ // Start async validation (modelValue is non-empty so the custom rule runs)
1931
+ const p1 = result.validate()
1932
+
1933
+ // Toggle readonly — next validate() should short-circuit and return true immediately
1934
+ params.readonly.value = true
1935
+ const valid = await result.validate()
1936
+ expect(valid).toBe(true)
1937
+ // Errors are cleared by the readonly short-circuit
1938
+ expect(result.errors.value).toEqual([])
1939
+
1940
+ // Resolve stale async — since the readonly short-circuit does not
1941
+ // invalidate the pending token, the resolved result still writes errors
1942
+ resolve(false)
1943
+ await p1
1944
+ expect(result.errors.value).toContain('Erreur obsolète readonly')
1945
+ })
1946
+ })
1947
+
1948
+ describe('async validation rules that throw errors', () => {
1949
+ it('handles thrown error in async custom rules and uses the custom message', async () => {
1950
+ const params = makeParams({
1951
+ modelValue: ref('test'),
1952
+ customRules: ref([{
1953
+ type: 'custom',
1954
+ options: {
1955
+ validate: async () => {
1956
+ throw new Error('Network error')
1957
+ },
1958
+ message: 'Erreur personnalisée',
1959
+ },
1960
+ }]),
1961
+ })
1962
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1963
+
1964
+ const valid = await result.validate()
1965
+ expect(valid).toBe(false)
1966
+ expect(result.errors.value).toContain('Erreur personnalisée')
1967
+ expect(result.hasError.value).toBe(true)
1968
+ })
1969
+
1970
+ it('uses the thrown error message when no custom message is provided', async () => {
1971
+ const params = makeParams({
1972
+ modelValue: ref('test'),
1973
+ customRules: ref([{
1974
+ type: 'custom',
1975
+ options: {
1976
+ validate: async () => {
1977
+ throw new Error('Service unavailable')
1978
+ },
1979
+ },
1980
+ }]),
1981
+ })
1982
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
1983
+
1984
+ const valid = await result.validate()
1985
+ expect(valid).toBe(false)
1986
+ expect(result.errors.value).toContain('Service unavailable')
1987
+ expect(result.hasError.value).toBe(true)
1988
+ })
1989
+
1990
+ it('handles thrown error in async warning rules', async () => {
1991
+ const params = makeParams({
1992
+ modelValue: ref('test'),
1993
+ customWarningRules: ref([{
1994
+ type: 'custom',
1995
+ options: {
1996
+ validate: async () => {
1997
+ throw new Error('Warning service failed')
1998
+ },
1999
+ isWarning: true,
2000
+ },
2001
+ }]),
2002
+ })
2003
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
2004
+
2005
+ await result.validate()
2006
+ expect(result.warnings.value).toContain('Warning service failed')
2007
+ expect(result.hasWarning.value).toBe(true)
2008
+ })
2009
+
2010
+ it('handles thrown error in async success rules gracefully', async () => {
2011
+ const params = makeParams({
2012
+ modelValue: ref('test'),
2013
+ customSuccessRules: ref([{
2014
+ type: 'custom',
2015
+ options: {
2016
+ validate: async () => {
2017
+ throw new Error('Success check failed')
2018
+ },
2019
+ },
2020
+ }]),
2021
+ })
2022
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
2023
+
2024
+ await result.validate()
2025
+ // The thrown error is caught and does not crash — no validation errors are surfaced
2026
+ expect(result.errors.value).toEqual([])
2027
+ })
2028
+
2029
+ it('handles non-Error thrown values in async rules', async () => {
2030
+ const params = makeParams({
2031
+ modelValue: ref('test'),
2032
+ customRules: ref([{
2033
+ type: 'custom',
2034
+ options: {
2035
+ validate: async () => {
2036
+ throw 'string error'
2037
+ },
2038
+ },
2039
+ }]),
2040
+ })
2041
+ const { result } = withSetup(() => useValidation(params as Parameters<typeof useValidation>[0]))
2042
+
2043
+ const valid = await result.validate()
2044
+ expect(valid).toBe(false)
2045
+ expect(result.errors.value).toContain('string error')
2046
+ })
2047
+ })
2048
+ })