@cnamts/synapse 1.0.9 → 1.0.10

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 (393) hide show
  1. package/dist/{DateFilter-BylukfjR.js → DateFilter-C0wDuzgn.js} +1 -1
  2. package/dist/{NumberFilter-C_bUk9o1.js → NumberFilter-CBj7zdOi.js} +1 -1
  3. package/dist/{PeriodFilter-dVOmcfmt.js → PeriodFilter-DB4wWyKy.js} +1 -1
  4. package/dist/{SelectFilter-BW8KpXkQ.js → SelectFilter-Dces8572.js} +1 -1
  5. package/dist/{TextFilter-diwVzTz7.js → TextFilter-BU9nlkuS.js} +1 -1
  6. package/dist/components/Amelipro/AmeliproAccordionFrieze/AmeliproAccordionFrieze.d.ts +110 -0
  7. package/dist/components/Amelipro/AmeliproAccordionFrieze/types.d.ts +6 -0
  8. package/dist/components/Amelipro/AmeliproAutoCompleteField/AmeliproAutoCompleteField.d.ts +120 -120
  9. package/dist/components/Amelipro/AmeliproBtn/AmeliproBtn.d.ts +2 -2
  10. package/dist/components/Amelipro/AmeliproCarousel/AmeliproCarousel.d.ts +3 -3
  11. package/dist/components/Amelipro/AmeliproDentalChart/AmeliproDentalChart.d.ts +47 -0
  12. package/dist/components/Amelipro/AmeliproDentalChart/AmeliproTooth/AmeliproTooth.d.ts +88 -0
  13. package/dist/components/Amelipro/AmeliproDentalChart/types.d.ts +13 -0
  14. package/dist/components/Amelipro/AmeliproFirstLogin/AmeliproFirstLogin.d.ts +70 -0
  15. package/dist/components/Amelipro/AmeliproFirstLogin/locales.d.ts +46 -0
  16. package/dist/components/Amelipro/AmeliproIconBtn/AmeliproIconBtn.d.ts +1 -1
  17. package/dist/components/Amelipro/AmeliproPatientLogged/AmeliproPatientLogged.d.ts +149 -0
  18. package/dist/components/Amelipro/AmeliproPatientLogged/types.d.ts +48 -0
  19. package/dist/components/Amelipro/AmeliproPatientLogin/AmeliproPatientLogin.d.ts +177 -0
  20. package/dist/components/Amelipro/AmeliproPatientLogin/AmeliproPatientLoginForm/AmeliproPatientLoginForm.d.ts +169 -0
  21. package/dist/components/Amelipro/AmeliproPatientLogin/AmeliproPatientLoginForm/types.d.ts +5 -0
  22. package/dist/components/Amelipro/AmeliproPatientLogin/types.d.ts +5 -0
  23. package/dist/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressCityRow/AmeliproPostalAddressCityRow.d.ts +2925 -0
  24. package/dist/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressCityRow/types.d.ts +11 -0
  25. package/dist/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressField.d.ts +4489 -0
  26. package/dist/components/Amelipro/AmeliproPostalAddressField/types.d.ts +17 -0
  27. package/dist/components/Amelipro/AmeliproSelect/AmeliproSelect.d.ts +120 -120
  28. package/dist/components/Amelipro/AmeliproTabs/AmeliproTabs.d.ts +120 -120
  29. package/dist/components/Amelipro/AmeliproTextArea/AmeliproTextArea.d.ts +12 -12
  30. package/dist/components/Amelipro/AmeliproTextField/AmeliproTextField.d.ts +30 -30
  31. package/dist/components/Amelipro/AmeliproTooltips/AmeliproTooltips.d.ts +12 -2
  32. package/dist/components/CookieBanner/CookieBanner.d.ts +7 -7
  33. package/dist/components/CookiesSelection/CookiesInformation/CookiesInformation.d.ts +20 -20
  34. package/dist/components/CookiesSelection/CookiesSelection.d.ts +9 -9
  35. package/dist/components/Customs/Selects/SySelect/SySelect.d.ts +7 -6
  36. package/dist/components/Customs/SyForm/SyForm.d.ts +1820 -0
  37. package/dist/components/Customs/SyTabs/SyTabs.d.ts +3 -1
  38. package/dist/components/Customs/SyTabs/useTabTransition.d.ts +5 -0
  39. package/dist/components/Customs/SyTextField/SyTextField.d.ts +36 -31
  40. package/dist/components/Customs/SyTextField/types.d.ts +1 -1
  41. package/dist/components/DatePicker/CalendarMode/DatePicker.d.ts +232 -134
  42. package/dist/components/DatePicker/ComplexDatePicker/ComplexDatePicker.d.ts +121 -71
  43. package/dist/components/DatePicker/DateTextInput/DateTextInput.d.ts +60 -37
  44. package/dist/components/DatePicker/composables/useDateValidation.d.ts +14 -2
  45. package/dist/components/DatePicker/composables/useManualDateValidation.d.ts +2 -2
  46. package/dist/components/DatePicker/tests/setup.d.ts +74354 -0
  47. package/dist/components/DialogBox/DialogBox.d.ts +10 -10
  48. package/dist/components/DialogBox/locales.d.ts +1 -0
  49. package/dist/components/DialogBox/useDraggable.d.ts +2 -0
  50. package/dist/components/FilterSideBar/FilterSideBar.d.ts +7 -7
  51. package/dist/components/HeaderBar/HeaderBurgerMenu/HeaderMenuItem/HeaderMenuItem.d.ts +2 -0
  52. package/dist/components/HeaderNavigationBar/types.d.ts +1 -0
  53. package/dist/components/HeaderToolbar/HeaderToolbar.d.ts +842 -9
  54. package/dist/components/HeaderToolbar/locales.d.ts +3 -0
  55. package/dist/components/HeaderToolbar/useMobileRightMenu.d.ts +10 -0
  56. package/dist/components/LangBtn/LangBtn.d.ts +8 -8
  57. package/dist/components/NirField/NirField.d.ts +90 -65
  58. package/dist/components/NirField/locales.d.ts +6 -4
  59. package/dist/components/PasswordField/PasswordField.d.ts +3 -1
  60. package/dist/components/PasswordField/locales.d.ts +2 -0
  61. package/dist/components/PeriodField/PeriodField.d.ts +460 -260
  62. package/dist/components/PhoneField/PhoneField.d.ts +45 -32
  63. package/dist/components/RangeField/RangeField.d.ts +11 -1
  64. package/dist/components/RangeField/RangeSlider/RangeSlider.d.ts +2 -2
  65. package/dist/components/RangeField/config.d.ts +3 -0
  66. package/dist/components/RatingPicker/EmotionPicker/EmotionPicker.d.ts +3 -1
  67. package/dist/components/RatingPicker/EmotionPicker/locales.d.ts +2 -0
  68. package/dist/components/RatingPicker/NumberPicker/NumberPicker.d.ts +3 -1
  69. package/dist/components/RatingPicker/StarsPicker/StarsPicker.d.ts +3 -1
  70. package/dist/components/RatingPicker/locales.d.ts +1 -0
  71. package/dist/components/SearchListField/SearchListField.d.ts +6 -6
  72. package/dist/components/SyAlert/SyAlert.d.ts +2 -2
  73. package/dist/components/SyTextArea/SyTextArea.d.ts +13 -13
  74. package/dist/components/Tables/SyServerTable/SyServerTable.d.ts +3 -5
  75. package/dist/components/Tables/SyTable/SyTable.d.ts +3 -5
  76. package/dist/components/Tables/common/SyTablePagination.d.ts +8 -6
  77. package/dist/components/Tables/common/organizeColumns/OrganizeColumns.d.ts +4 -4
  78. package/dist/components/Tables/common/usePagination.d.ts +1 -3
  79. package/dist/components/Tables/common/useTableCheckbox.d.ts +1 -1
  80. package/dist/components/UploadWorkflow/UploadWorkflow.d.ts +4 -4
  81. package/dist/components/index.d.ts +8 -0
  82. package/dist/composables/validation/useFormValidation.d.ts +24 -0
  83. package/dist/composables/validation/useValidatable.d.ts +17 -0
  84. package/dist/design-system-v3.js +181 -173
  85. package/dist/design-system-v3.umd.cjs +265 -263
  86. package/dist/{main-2eWGB7zZ.js → main-Dt4iNotT.js} +14095 -11151
  87. package/dist/style.css +1 -1
  88. package/dist/tooth-11-B9fN9Ow_.js +4 -0
  89. package/dist/tooth-12-BOOjuDe9.js +4 -0
  90. package/dist/tooth-13-DVU7jhZ8.js +4 -0
  91. package/dist/tooth-14-CXNleTBu.js +4 -0
  92. package/dist/tooth-15-iq3z8dzZ.js +4 -0
  93. package/dist/tooth-16-BuNIHSQk.js +4 -0
  94. package/dist/tooth-17-DLb4ijsH.js +4 -0
  95. package/dist/tooth-18-huijQe68.js +4 -0
  96. package/dist/tooth-21-Bl7Q-o4y.js +4 -0
  97. package/dist/tooth-22-ChQKI3h5.js +4 -0
  98. package/dist/tooth-23-CkzbEvBa.js +4 -0
  99. package/dist/tooth-24-BpaPUSEp.js +4 -0
  100. package/dist/tooth-25-BaVfhAL6.js +4 -0
  101. package/dist/tooth-26-BnL03Jv5.js +4 -0
  102. package/dist/tooth-27-BaHyZfhH.js +4 -0
  103. package/dist/tooth-28-BrMBVEgX.js +4 -0
  104. package/dist/tooth-31-DEH3Btej.js +4 -0
  105. package/dist/tooth-32-Dqcy596v.js +4 -0
  106. package/dist/tooth-33-DLzQOVky.js +4 -0
  107. package/dist/tooth-34-36nkjUPW.js +4 -0
  108. package/dist/tooth-35-VfFhleWT.js +4 -0
  109. package/dist/tooth-36-BHwtGkLx.js +4 -0
  110. package/dist/tooth-37-CT47Rtk-.js +4 -0
  111. package/dist/tooth-38-D15JmYSD.js +4 -0
  112. package/dist/tooth-41--x9N_iSc.js +4 -0
  113. package/dist/tooth-42-DZ1D3qmP.js +4 -0
  114. package/dist/tooth-43-C9T3b5_0.js +4 -0
  115. package/dist/tooth-44-CsPRBSZV.js +4 -0
  116. package/dist/tooth-45-Dg3wQunm.js +4 -0
  117. package/dist/tooth-46-DAOEt4G5.js +4 -0
  118. package/dist/tooth-47-DcqUeVM0.js +4 -0
  119. package/dist/tooth-48-0MVzkYem.js +4 -0
  120. package/dist/tooth-51-DOTod22I.js +4 -0
  121. package/dist/tooth-52-DZB1Jabv.js +4 -0
  122. package/dist/tooth-53-nunm2BQr.js +4 -0
  123. package/dist/tooth-54-BwdYfBd-.js +4 -0
  124. package/dist/tooth-55-BUJdNwqL.js +4 -0
  125. package/dist/tooth-61-BwqR1B88.js +4 -0
  126. package/dist/tooth-62-BzaECsvF.js +4 -0
  127. package/dist/tooth-63-wjdIfSq2.js +4 -0
  128. package/dist/tooth-64-CGW4ZcUq.js +4 -0
  129. package/dist/tooth-65-DxH4GgAL.js +4 -0
  130. package/dist/tooth-71-CmjVz11G.js +4 -0
  131. package/dist/tooth-72-CCyNUD-W.js +4 -0
  132. package/dist/tooth-73-D6aJwVz4.js +4 -0
  133. package/dist/tooth-74-zNtDQ6ig.js +4 -0
  134. package/dist/tooth-75-DDEx6y4E.js +4 -0
  135. package/dist/tooth-81-xg8UVvz2.js +4 -0
  136. package/dist/tooth-82-CtNXwBtB.js +4 -0
  137. package/dist/tooth-83-C2ODw7VT.js +4 -0
  138. package/dist/tooth-84-BKIdO9HA.js +4 -0
  139. package/dist/tooth-85-3YmvfAsK.js +4 -0
  140. package/package.json +2 -2
  141. package/src/assets/amelipro/img/dental-chart-img/tooth-11.svg +16 -0
  142. package/src/assets/amelipro/img/dental-chart-img/tooth-12.svg +11 -0
  143. package/src/assets/amelipro/img/dental-chart-img/tooth-13.svg +11 -0
  144. package/src/assets/amelipro/img/dental-chart-img/tooth-14.svg +26 -0
  145. package/src/assets/amelipro/img/dental-chart-img/tooth-15.svg +21 -0
  146. package/src/assets/amelipro/img/dental-chart-img/tooth-16.svg +31 -0
  147. package/src/assets/amelipro/img/dental-chart-img/tooth-17.svg +26 -0
  148. package/src/assets/amelipro/img/dental-chart-img/tooth-18.svg +26 -0
  149. package/src/assets/amelipro/img/dental-chart-img/tooth-21.svg +16 -0
  150. package/src/assets/amelipro/img/dental-chart-img/tooth-22.svg +11 -0
  151. package/src/assets/amelipro/img/dental-chart-img/tooth-23.svg +11 -0
  152. package/src/assets/amelipro/img/dental-chart-img/tooth-24.svg +26 -0
  153. package/src/assets/amelipro/img/dental-chart-img/tooth-25.svg +21 -0
  154. package/src/assets/amelipro/img/dental-chart-img/tooth-26.svg +31 -0
  155. package/src/assets/amelipro/img/dental-chart-img/tooth-27.svg +26 -0
  156. package/src/assets/amelipro/img/dental-chart-img/tooth-28.svg +26 -0
  157. package/src/assets/amelipro/img/dental-chart-img/tooth-31.svg +11 -0
  158. package/src/assets/amelipro/img/dental-chart-img/tooth-32.svg +11 -0
  159. package/src/assets/amelipro/img/dental-chart-img/tooth-33.svg +11 -0
  160. package/src/assets/amelipro/img/dental-chart-img/tooth-34.svg +11 -0
  161. package/src/assets/amelipro/img/dental-chart-img/tooth-35.svg +22 -0
  162. package/src/assets/amelipro/img/dental-chart-img/tooth-36.svg +26 -0
  163. package/src/assets/amelipro/img/dental-chart-img/tooth-37.svg +26 -0
  164. package/src/assets/amelipro/img/dental-chart-img/tooth-38.svg +11 -0
  165. package/src/assets/amelipro/img/dental-chart-img/tooth-41.svg +11 -0
  166. package/src/assets/amelipro/img/dental-chart-img/tooth-42.svg +11 -0
  167. package/src/assets/amelipro/img/dental-chart-img/tooth-43.svg +11 -0
  168. package/src/assets/amelipro/img/dental-chart-img/tooth-44.svg +11 -0
  169. package/src/assets/amelipro/img/dental-chart-img/tooth-45.svg +22 -0
  170. package/src/assets/amelipro/img/dental-chart-img/tooth-46.svg +26 -0
  171. package/src/assets/amelipro/img/dental-chart-img/tooth-47.svg +26 -0
  172. package/src/assets/amelipro/img/dental-chart-img/tooth-48.svg +11 -0
  173. package/src/assets/amelipro/img/dental-chart-img/tooth-51.svg +11 -0
  174. package/src/assets/amelipro/img/dental-chart-img/tooth-52.svg +16 -0
  175. package/src/assets/amelipro/img/dental-chart-img/tooth-53.svg +16 -0
  176. package/src/assets/amelipro/img/dental-chart-img/tooth-54.svg +11 -0
  177. package/src/assets/amelipro/img/dental-chart-img/tooth-55.svg +16 -0
  178. package/src/assets/amelipro/img/dental-chart-img/tooth-61.svg +11 -0
  179. package/src/assets/amelipro/img/dental-chart-img/tooth-62.svg +16 -0
  180. package/src/assets/amelipro/img/dental-chart-img/tooth-63.svg +16 -0
  181. package/src/assets/amelipro/img/dental-chart-img/tooth-64.svg +11 -0
  182. package/src/assets/amelipro/img/dental-chart-img/tooth-65.svg +16 -0
  183. package/src/assets/amelipro/img/dental-chart-img/tooth-71.svg +11 -0
  184. package/src/assets/amelipro/img/dental-chart-img/tooth-72.svg +16 -0
  185. package/src/assets/amelipro/img/dental-chart-img/tooth-73.svg +16 -0
  186. package/src/assets/amelipro/img/dental-chart-img/tooth-74.svg +11 -0
  187. package/src/assets/amelipro/img/dental-chart-img/tooth-75.svg +16 -0
  188. package/src/assets/amelipro/img/dental-chart-img/tooth-81.svg +11 -0
  189. package/src/assets/amelipro/img/dental-chart-img/tooth-82.svg +16 -0
  190. package/src/assets/amelipro/img/dental-chart-img/tooth-83.svg +16 -0
  191. package/src/assets/amelipro/img/dental-chart-img/tooth-84.svg +11 -0
  192. package/src/assets/amelipro/img/dental-chart-img/tooth-85.svg +16 -0
  193. package/src/assets/amelipro/img/idpa/apcv_logo.svg +16 -0
  194. package/src/assets/amelipro/img/idpa/carte-vitale.svg +75 -0
  195. package/src/components/Amelipro/AmeliproAccordionFrieze/AmeliproAccordionFrieze.mdx +15 -0
  196. package/src/components/Amelipro/AmeliproAccordionFrieze/AmeliproAccordionFrieze.stories.ts +261 -0
  197. package/src/components/Amelipro/AmeliproAccordionFrieze/AmeliproAccordionFrieze.vue +419 -0
  198. package/src/components/Amelipro/AmeliproAccordionFrieze/__tests__/AmeliproAccordionFrieze.spec.ts +98 -0
  199. package/src/components/Amelipro/AmeliproAccordionFrieze/__tests__/__snapshots__/AmeliproAccordionFrieze.spec.ts.snap +858 -0
  200. package/src/components/Amelipro/AmeliproAccordionFrieze/types.d.ts +6 -0
  201. package/src/components/Amelipro/AmeliproAccordionList/AmeliproAccordionList.vue +2 -2
  202. package/src/components/Amelipro/AmeliproAccordionList/__tests__/__snapshots__/AmeliproAccordionList.spec.ts.snap +0 -4
  203. package/src/components/Amelipro/AmeliproAccordionResultList/AmeliproAccordionResultList.vue +2 -2
  204. package/src/components/Amelipro/AmeliproAccordionResultList/__tests__/__snapshots__/AmeliproAccordionResultList.spec.ts.snap +0 -4
  205. package/src/components/Amelipro/AmeliproBadge/AmeliproBadge.stories.ts +97 -0
  206. package/src/components/Amelipro/AmeliproBtn/AmeliproBtn.stories.ts +190 -8
  207. package/src/components/Amelipro/AmeliproCallback/AmeliproCallback.stories.ts +321 -0
  208. package/src/components/Amelipro/AmeliproCaptcha/AmeliproCaptcha.vue +1 -1
  209. package/src/components/Amelipro/AmeliproCard/AmeliproCard.stories.ts +360 -0
  210. package/src/components/Amelipro/AmeliproCheckbox/AmeliproCheckbox.stories.ts +1 -1
  211. package/src/components/Amelipro/AmeliproCheckboxGroup/AmeliproCheckboxGroup.stories.ts +2 -2
  212. package/src/components/Amelipro/AmeliproCheckboxGroup/AmeliproCheckboxGroup.vue +1 -1
  213. package/src/components/Amelipro/AmeliproChips/AmeliproChips.stories.ts +46 -0
  214. package/src/components/Amelipro/AmeliproClickableTile/AmeliproClickableTile.stories.ts +173 -0
  215. package/src/components/Amelipro/AmeliproCopyBtn/AmeliproCopyBtn.stories.ts +112 -26
  216. package/src/components/Amelipro/AmeliproDentalChart/AmeliproDentalChart.mdx +15 -0
  217. package/src/components/Amelipro/AmeliproDentalChart/AmeliproDentalChart.stories.ts +1078 -0
  218. package/src/components/Amelipro/AmeliproDentalChart/AmeliproDentalChart.vue +163 -0
  219. package/src/components/Amelipro/AmeliproDentalChart/AmeliproTooth/AmeliproTooth.vue +183 -0
  220. package/src/components/Amelipro/AmeliproDentalChart/AmeliproTooth/tests/AmeliproTooth.spec.ts +20 -0
  221. package/src/components/Amelipro/AmeliproDentalChart/AmeliproTooth/tests/__snapshots__/AmeliproTooth.spec.ts.snap +39 -0
  222. package/src/components/Amelipro/AmeliproDentalChart/tests/AmeliproDentalChart.spec.ts +468 -0
  223. package/src/components/Amelipro/AmeliproDentalChart/tests/__snapshots__/AmeliproDentalChart.spec.ts.snap +2589 -0
  224. package/src/components/Amelipro/AmeliproDentalChart/types.d.ts +13 -0
  225. package/src/components/Amelipro/AmeliproDialog/AmeliproDialog.stories.ts +333 -0
  226. package/src/components/Amelipro/AmeliproDialog/AmeliproDialog.vue +7 -2
  227. package/src/components/Amelipro/AmeliproDialog/tests/__snapshots__/AmeliproDialog.spec.ts.snap +1 -0
  228. package/src/components/Amelipro/AmeliproErrorTemplate/AmeliproErrorTemplate.vue +1 -1
  229. package/src/components/Amelipro/AmeliproErrorTemplate/__tests__/__snapshots__/AmeliproErrorTemplate.spec.ts.snap +1 -2
  230. package/src/components/Amelipro/AmeliproFirstLogin/AmeliproFirstLogin.mdx +15 -0
  231. package/src/components/Amelipro/AmeliproFirstLogin/AmeliproFirstLogin.stories.ts +287 -0
  232. package/src/components/Amelipro/AmeliproFirstLogin/AmeliproFirstLogin.vue +331 -0
  233. package/src/components/Amelipro/AmeliproFirstLogin/__tests__/AmeliproFirstLogin.spec.ts +29 -0
  234. package/src/components/Amelipro/AmeliproFirstLogin/__tests__/__snapshots__/AmeliproFirstLogin.spec.ts.snap +1210 -0
  235. package/src/components/Amelipro/AmeliproFirstLogin/locales.ts +48 -0
  236. package/src/components/Amelipro/AmeliproIcon/AmeliproIcon.stories.ts +61 -1
  237. package/src/components/Amelipro/AmeliproIconBtn/AmeliproIconBtn.stories.ts +174 -0
  238. package/src/components/Amelipro/AmeliproIllustratedDataTile/AmeliproIllustratedDataTile.stories.ts +186 -0
  239. package/src/components/Amelipro/AmeliproMailTile/AmeliproMailTile.stories.ts +237 -0
  240. package/src/components/Amelipro/AmeliproMessage/AmeliproMessage.stories.ts +111 -0
  241. package/src/components/Amelipro/AmeliproMultipleFoldingCard/AmeliproMultipleFoldingCard.stories.ts +199 -0
  242. package/src/components/Amelipro/AmeliproNumberedCard/AmeliproNumberedCard.stories.ts +150 -0
  243. package/src/components/Amelipro/AmeliproOnboarding/AmeliproOnboarding.stories.ts +281 -8
  244. package/src/components/Amelipro/AmeliproPagination/AmeliproPagination.stories.ts +123 -43
  245. package/src/components/Amelipro/AmeliproPatientLogged/AmeliproPatientLogged.mdx +18 -0
  246. package/src/components/Amelipro/AmeliproPatientLogged/AmeliproPatientLogged.stories.ts +250 -0
  247. package/src/components/Amelipro/AmeliproPatientLogged/AmeliproPatientLogged.vue +520 -0
  248. package/src/components/Amelipro/AmeliproPatientLogged/__tests__/AmeliproPatientLogged.spec.ts +79 -0
  249. package/src/components/Amelipro/AmeliproPatientLogged/__tests__/__snapshots__/AmeliproPatientLogged.spec.ts.snap +1140 -0
  250. package/src/components/Amelipro/AmeliproPatientLogged/types.d.ts +49 -0
  251. package/src/components/Amelipro/AmeliproPatientLogin/AmeliproPatientLogin.mdx +18 -0
  252. package/src/components/Amelipro/AmeliproPatientLogin/AmeliproPatientLogin.stories.ts +151 -0
  253. package/src/components/Amelipro/AmeliproPatientLogin/AmeliproPatientLogin.vue +231 -0
  254. package/src/components/Amelipro/AmeliproPatientLogin/AmeliproPatientLoginForm/AmeliproPatientLoginForm.vue +252 -0
  255. package/src/components/Amelipro/AmeliproPatientLogin/AmeliproPatientLoginForm/__tests__/AmeliproPatientLoginForm.spec.ts +46 -0
  256. package/src/components/Amelipro/AmeliproPatientLogin/AmeliproPatientLoginForm/__tests__/__snapshots__/AmeliproPatientLoginForm.spec.ts.snap +33 -0
  257. package/src/components/Amelipro/AmeliproPatientLogin/AmeliproPatientLoginForm/types.d.ts +6 -0
  258. package/src/components/Amelipro/AmeliproPatientLogin/__tests__/AmeliproPatientLogin.spec.ts +49 -0
  259. package/src/components/Amelipro/AmeliproPatientLogin/__tests__/__snapshots__/AmeliproPatientLogin.spec.ts.snap +60 -0
  260. package/src/components/Amelipro/AmeliproPatientLogin/types.d.ts +6 -0
  261. package/src/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressCityRow/AmeliproPostalAddressCityRow.vue +464 -0
  262. package/src/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressCityRow/__tests__/AmeliproPostalAddressCityRow.spec.ts +41 -0
  263. package/src/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressCityRow/__tests__/__snapshots__/AmeliproPostalAddressCityRow.spec.ts.snap +548 -0
  264. package/src/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressCityRow/types.d.ts +12 -0
  265. package/src/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressField.mdx +15 -0
  266. package/src/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressField.stories.ts +133 -0
  267. package/src/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressField.vue +360 -0
  268. package/src/components/Amelipro/AmeliproPostalAddressField/tests/AmeliproPostalAddressField.spec.ts +27 -0
  269. package/src/components/Amelipro/AmeliproPostalAddressField/tests/__snapshots__/AmeliproPostalAddressField.spec.ts.snap +548 -0
  270. package/src/components/Amelipro/AmeliproPostalAddressField/types.d.ts +18 -0
  271. package/src/components/Amelipro/AmeliproRadioGroup/AmeliproRadioGroup.vue +4 -3
  272. package/src/components/Amelipro/AmeliproResultList/AmeliproResultList.vue +2 -2
  273. package/src/components/Amelipro/AmeliproResultList/__tests__/__snapshots__/AmeliproResultList.spec.ts.snap +0 -4
  274. package/src/components/Amelipro/AmeliproStateTile/AmeliproStateTile.stories.ts +210 -0
  275. package/src/components/Amelipro/AmeliproStatus/AmeliproStatus.stories.ts +326 -0
  276. package/src/components/Amelipro/AmeliproTable/AmeliproTable.stories.ts +232 -4
  277. package/src/components/Amelipro/AmeliproTable/AmeliproTable.vue +3 -2
  278. package/src/components/Amelipro/AmeliproTable/__tests__/__snapshots__/AmeliproTable.spec.ts.snap +0 -4
  279. package/src/components/Amelipro/AmeliproTabs/AmeliproTabs.vue +1 -1
  280. package/src/components/Amelipro/AmeliproTextField/AmeliproTextField.stories.ts +2 -2
  281. package/src/components/Amelipro/AmeliproTileBtn/AmeliproTileBtn.stories.ts +222 -6
  282. package/src/components/Amelipro/AmeliproTooltips/AmeliproTooltips.stories.ts +169 -0
  283. package/src/components/Amelipro/AmeliproTooltips/AmeliproTooltips.vue +6 -1
  284. package/src/components/Amelipro/AmeliproTooltips/tests/__snapshots__/AmeliproTooltips.spec.ts.snap +1 -5
  285. package/src/components/Amelipro/AmeliproTransmission/AmeliproTransmission.stories.ts +160 -0
  286. package/src/components/Amelipro/StructureMenu/StructureMenu.vue +1 -1
  287. package/src/components/Amelipro/StructureMenu/tests/__snapshots__/StructureMenu.spec.ts.snap +1 -2
  288. package/src/components/Customs/Selects/SySelect/SySelect.vue +43 -1
  289. package/src/components/Customs/SyCheckbox/SyCheckbox.vue +4 -0
  290. package/src/components/Customs/SyForm/Introduction.mdx +132 -0
  291. package/src/components/Customs/SyForm/SyForm.mdx +105 -0
  292. package/src/components/Customs/SyForm/SyForm.stories.ts +375 -0
  293. package/src/components/Customs/SyForm/SyForm.vue +80 -0
  294. package/src/components/Customs/SyTabs/SyTabs.stories.ts +31 -0
  295. package/src/components/Customs/SyTabs/SyTabs.vue +185 -27
  296. package/src/components/Customs/SyTabs/useTabTransition.ts +42 -0
  297. package/src/components/Customs/SyTextField/SyTextField.vue +12 -3
  298. package/src/components/Customs/SyTextField/types.d.ts +1 -1
  299. package/src/components/DataListItem/DataListItem.vue +12 -13
  300. package/src/components/DatePicker/CalendarMode/DatePicker.stories.ts +242 -41
  301. package/src/components/DatePicker/CalendarMode/DatePicker.vue +30 -12
  302. package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.stories.ts +246 -59
  303. package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.vue +40 -10
  304. package/src/components/DatePicker/DatePickerOverview.mdx +5 -1
  305. package/src/components/DatePicker/DatePickerValidationExample/DatePickerValidation.mdx +21 -21
  306. package/src/components/DatePicker/DatePickerValidationExample/DatePickerValidation.stories.ts +240 -133
  307. package/src/components/DatePicker/DateTextInput/DateRange.stories.ts +29 -1
  308. package/src/components/DatePicker/DateTextInput/DateTextInput.vue +32 -9
  309. package/src/components/DatePicker/DateTextInput/NoCalendar.stories.ts +166 -38
  310. package/src/components/DatePicker/composables/useDateValidation.ts +8 -5
  311. package/src/components/DatePicker/composables/useManualDateValidation.ts +23 -6
  312. package/src/components/DatePicker/datePickers.stories.ts +28 -0
  313. package/src/components/DatePicker/docExamples/DatePickerBidirectionalValidation.vue +47 -58
  314. package/src/components/DatePicker/docExamples/DatePickerValidationExamples.vue +2 -2
  315. package/src/components/DatePicker/tests/DatePicker.validation.spec.ts +4654 -0
  316. package/src/components/DatePicker/tests/archiTest.md +33 -0
  317. package/src/components/DatePicker/tests/setup.ts +243 -0
  318. package/src/components/DialogBox/DialogBox.stories.ts +1 -1
  319. package/src/components/DialogBox/DialogBox.vue +25 -8
  320. package/src/components/DialogBox/locales.ts +1 -0
  321. package/src/components/DialogBox/tests/DialogBox.spec.ts +187 -15
  322. package/src/components/DialogBox/useDraggable.ts +92 -4
  323. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuItem/HeaderMenuItem.vue +4 -0
  324. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuItem/tests/__snapshots__/HeaderMenuItem.spec.ts.snap +4 -1
  325. package/src/components/HeaderNavigationBar/HeaderNavigationBar.vue +18 -2
  326. package/src/components/HeaderNavigationBar/HorizontalNavbar/HorizontalNavbar.vue +14 -4
  327. package/src/components/HeaderNavigationBar/types.ts +1 -0
  328. package/src/components/HeaderToolbar/Accessibilite.stories.ts +8 -0
  329. package/src/components/HeaderToolbar/HeaderToolbar.mdx +0 -1
  330. package/src/components/HeaderToolbar/HeaderToolbar.stories.ts +65 -6
  331. package/src/components/HeaderToolbar/HeaderToolbar.vue +589 -162
  332. package/src/components/HeaderToolbar/locales.ts +3 -0
  333. package/src/components/HeaderToolbar/useMobileRightMenu.ts +121 -0
  334. package/src/components/NirField/Accessibilite.stories.ts +4 -0
  335. package/src/components/NirField/NirField.stories.ts +0 -11
  336. package/src/components/NirField/NirField.vue +164 -53
  337. package/src/components/NirField/locales.ts +6 -4
  338. package/src/components/NirField/tests/NirField.spec.ts +2 -2
  339. package/src/components/PasswordField/Accessibilite.stories.ts +96 -0
  340. package/src/components/PasswordField/PasswordField.stories.ts +8 -1
  341. package/src/components/PasswordField/PasswordField.vue +98 -19
  342. package/src/components/PasswordField/locales.ts +2 -0
  343. package/src/components/PasswordField/tests/PasswordField.spec.ts +1 -1
  344. package/src/components/PhoneField/PhoneField.vue +4 -0
  345. package/src/components/RangeField/Accessibilite.stories.ts +4 -0
  346. package/src/components/RangeField/RangeField.stories.ts +60 -0
  347. package/src/components/RangeField/RangeField.vue +37 -21
  348. package/src/components/RangeField/RangeSlider/RangeSlider.vue +30 -8
  349. package/src/components/RangeField/RangeSlider/Tooltip/Tooltip.vue +1 -0
  350. package/src/components/RangeField/RangeSlider/tests/__snapshots__/rangeSlider.spec.ts.snap +12 -4
  351. package/src/components/RangeField/RangeSlider/tests/useMouseSlide.spec.ts +9 -1
  352. package/src/components/RangeField/RangeSlider/useMouseSlide.ts +23 -8
  353. package/src/components/RangeField/config.ts +3 -1
  354. package/src/components/RangeField/tests/__snapshots__/RangeField.spec.ts.snap +132 -141
  355. package/src/components/RatingPicker/Accessibilite.stories.ts +4 -0
  356. package/src/components/RatingPicker/EmotionPicker/EmotionPicker.vue +119 -66
  357. package/src/components/RatingPicker/EmotionPicker/locales.ts +2 -0
  358. package/src/components/RatingPicker/EmotionPicker/tests/EmotionPicker.spec.ts +4 -4
  359. package/src/components/RatingPicker/EmotionPicker/tests/__snapshots__/EmotionPicker.spec.ts.snap +462 -678
  360. package/src/components/RatingPicker/NumberPicker/NumberPicker.vue +106 -42
  361. package/src/components/RatingPicker/NumberPicker/tests/NumberPicker.spec.ts +3 -3
  362. package/src/components/RatingPicker/NumberPicker/tests/__snapshots__/NumberPicker.spec.ts.snap +119 -515
  363. package/src/components/RatingPicker/Rating.ts +5 -1
  364. package/src/components/RatingPicker/RatingPicker.stories.ts +5 -6
  365. package/src/components/RatingPicker/RatingPicker.vue +7 -2
  366. package/src/components/RatingPicker/StarsPicker/StarsPicker.vue +78 -42
  367. package/src/components/RatingPicker/StarsPicker/tests/StarsPicker.spec.ts +7 -7
  368. package/src/components/RatingPicker/StarsPicker/tests/__snapshots__/StarsPicker.spec.ts.snap +163 -245
  369. package/src/components/RatingPicker/locales.ts +1 -0
  370. package/src/components/RatingPicker/tests/__snapshots__/RatingPicker.spec.ts.snap +120 -516
  371. package/src/components/Tables/SyServerTable/SyServerTable.stories.ts +109 -84
  372. package/src/components/Tables/SyServerTable/SyServerTable.vue +51 -16
  373. package/src/components/Tables/SyTable/SyTable.vue +1 -6
  374. package/src/components/Tables/SyTable/tests/SyTable.spec.ts +6 -0
  375. package/src/components/Tables/common/SyTablePagination.vue +0 -6
  376. package/src/components/Tables/common/tableProps.ts +19 -1
  377. package/src/components/Tables/common/usePagination.ts +0 -6
  378. package/src/components/Tables/common/useTableCheckbox.ts +5 -5
  379. package/src/components/index.ts +8 -0
  380. package/src/composables/rules/useFieldValidation.ts +21 -3
  381. package/src/composables/validation/AvecVosComposants.mdx +145 -0
  382. package/src/composables/validation/FormValidation.mdx +151 -0
  383. package/src/composables/validation/FormValidation.stories.ts +402 -0
  384. package/src/composables/validation/useFormValidation.ts +91 -0
  385. package/src/composables/validation/useValidatable.ts +41 -0
  386. package/src/stories/Accessibilite/Avancement/Avancement.stories.ts +2 -2
  387. package/src/stories/Accessibilite/KitDePreAudit/Outils/Introduction.mdx +2 -2
  388. package/src/stories/Accessibilite/KitDePreAudit/Preaudit.mdx +2 -2
  389. package/src/stories/Demarrer/Accueil.stories.ts +6 -6
  390. package/src/stories/GuideDuDev/Installation.mdx +13 -0
  391. package/src/stories/GuideDuDev/LesBreackingChanges.mdx +1 -1
  392. package/src/stories/GuideDuDev/MigrationDepuisBridge.mdx +1 -1
  393. package/src/stories/GuideDuDev/MigrationDepuisVue2.mdx +1 -1
@@ -0,0 +1,4654 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import { nextTick, ref, computed } from 'vue'
4
+ import { vuetify } from '../../../../tests/unit/setup'
5
+ import DatePicker from '@/components/DatePicker/CalendarMode/DatePicker.vue'
6
+ /**
7
+ * Tests de validation complets pour tous les modes DatePicker
8
+ *
9
+ * Ce fichier couvre tous les cas de validation pour les 3 modes DatePicker :
10
+ * 1. CalendarMode - Mode calendrier classique
11
+ * 2. DatePicker - Mode complexe standard
12
+ * 3. DatePicker avec useCombinedMode - Mode combiné
13
+ * 4. DatePicker - Mode saisie texte uniquement
14
+ *
15
+ * Basé sur les mémoires de bugs résolus et les cas edge identifiés.
16
+ */
17
+
18
+ // Import des composants DatePicker
19
+
20
+ describe('DatePicker - Tests de Validation Complets', () => {
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- tests
22
+ let wrapper: any
23
+
24
+ beforeEach(() => {
25
+ vi.clearAllMocks()
26
+ })
27
+
28
+ afterEach(() => {
29
+ if (wrapper) {
30
+ wrapper.unmount()
31
+ }
32
+ })
33
+
34
+ /**
35
+ * TESTS POUR CALENDARMODE DATEPICKER
36
+ */
37
+ describe('CalendarMode DatePicker', () => {
38
+ describe('Cas normaux - Fonctionnement de base', () => {
39
+ it('doit afficher le composant avec les props par défaut', async () => {
40
+ wrapper = mount(DatePicker, {
41
+ global: {
42
+ plugins: [vuetify],
43
+ },
44
+ props: {
45
+ modelValue: null,
46
+ label: 'Date de test',
47
+ },
48
+ })
49
+
50
+ await nextTick()
51
+
52
+ expect(wrapper.exists()).toBe(true)
53
+ expect(wrapper.find('input').exists()).toBe(true)
54
+ expect(wrapper.find('label').text()).toContain('Date de test')
55
+ })
56
+
57
+ it('doit accepter une valeur initiale', async () => {
58
+ const initialDate = '2024-06-15'
59
+ wrapper = mount(DatePicker, {
60
+ global: {
61
+ plugins: [vuetify],
62
+ },
63
+ props: {
64
+ modelValue: initialDate,
65
+ label: 'Date avec valeur initiale',
66
+ },
67
+ })
68
+
69
+ await nextTick()
70
+
71
+ // Vérifier que le composant existe
72
+ expect(wrapper.exists()).toBe(true)
73
+ expect(wrapper.props('modelValue')).toBe(initialDate)
74
+ })
75
+
76
+ it('doit gérer le format européen DD/MM/YYYY', async () => {
77
+ wrapper = mount(DatePicker, {
78
+ global: {
79
+ plugins: [vuetify],
80
+ },
81
+ props: {
82
+ modelValue: null,
83
+ label: 'Date format européen',
84
+ format: 'DD/MM/YYYY',
85
+ },
86
+ })
87
+
88
+ await nextTick()
89
+
90
+ expect(wrapper.exists()).toBe(true)
91
+ })
92
+
93
+ it('doit gérer les différents formats de date', async () => {
94
+ const formats = ['DD/MM/YYYY', 'MM/DD/YYYY', 'YYYY-MM-DD']
95
+
96
+ for (const format of formats) {
97
+ wrapper = mount(DatePicker, {
98
+ global: {
99
+ plugins: [vuetify],
100
+ },
101
+ props: {
102
+ modelValue: null,
103
+ label: `Date format ${format}`,
104
+ format,
105
+ },
106
+ })
107
+
108
+ await nextTick()
109
+ expect(wrapper.exists()).toBe(true)
110
+ wrapper.unmount()
111
+ }
112
+ })
113
+
114
+ it('doit gérer le mode date de naissance', async () => {
115
+ wrapper = mount(DatePicker, {
116
+ global: {
117
+ plugins: [vuetify],
118
+ },
119
+ props: {
120
+ modelValue: null,
121
+ label: 'Date de naissance',
122
+ birthDate: true,
123
+ },
124
+ })
125
+
126
+ await nextTick()
127
+
128
+ expect(wrapper.exists()).toBe(true)
129
+ })
130
+
131
+ it('doit gérer les plages de dates', async () => {
132
+ wrapper = mount(DatePicker, {
133
+ global: {
134
+ plugins: [vuetify],
135
+ },
136
+ props: {
137
+ modelValue: null,
138
+ label: 'Plage de dates',
139
+ displayRange: true,
140
+ },
141
+ })
142
+
143
+ await nextTick()
144
+
145
+ expect(wrapper.exists()).toBe(true)
146
+ })
147
+ })
148
+
149
+ describe('États et propriétés', () => {
150
+ it('doit gérer l\'état required', async () => {
151
+ wrapper = mount(DatePicker, {
152
+ global: {
153
+ plugins: [vuetify],
154
+ },
155
+ props: {
156
+ modelValue: null,
157
+ label: 'Date obligatoire',
158
+ required: true,
159
+ },
160
+ })
161
+
162
+ await nextTick()
163
+
164
+ const result = wrapper.vm.validateOnSubmit()
165
+ expect(result).toBe(false)
166
+ })
167
+
168
+ it('doit gérer l\'état disabled', async () => {
169
+ wrapper = mount(DatePicker, {
170
+ global: {
171
+ plugins: [vuetify],
172
+ },
173
+ props: {
174
+ modelValue: null,
175
+ label: 'Date désactivée',
176
+ disabled: true,
177
+ },
178
+ })
179
+
180
+ await nextTick()
181
+
182
+ const input = wrapper.find('input')
183
+ expect(input.attributes('disabled')).toBeDefined()
184
+ })
185
+
186
+ it('doit gérer l\'état readonly', async () => {
187
+ wrapper = mount(DatePicker, {
188
+ global: {
189
+ plugins: [vuetify],
190
+ },
191
+ props: {
192
+ modelValue: '2024-06-15',
193
+ label: 'Date lecture seule',
194
+ readonly: true,
195
+ },
196
+ })
197
+
198
+ await nextTick()
199
+
200
+ const input = wrapper.find('input')
201
+ expect(input.attributes('readonly')).toBeDefined()
202
+ })
203
+ })
204
+
205
+ describe('Icônes et affichage', () => {
206
+ it('doit afficher l\'icône par défaut', async () => {
207
+ wrapper = mount(DatePicker, {
208
+ global: {
209
+ plugins: [vuetify],
210
+ },
211
+ props: {
212
+ modelValue: null,
213
+ label: 'Date avec icône',
214
+ },
215
+ })
216
+
217
+ await nextTick()
218
+
219
+ expect(wrapper.find('.v-icon').exists()).toBe(true)
220
+ })
221
+
222
+ it('doit masquer les icônes avec noIcon', async () => {
223
+ wrapper = mount(DatePicker, {
224
+ global: {
225
+ plugins: [vuetify],
226
+ },
227
+ props: {
228
+ modelValue: null,
229
+ label: 'Date sans icône',
230
+ noIcon: true,
231
+ },
232
+ })
233
+
234
+ await nextTick()
235
+
236
+ expect(wrapper.exists()).toBe(true)
237
+ })
238
+
239
+ it('doit gérer l\'icône en append', async () => {
240
+ wrapper = mount(DatePicker, {
241
+ global: {
242
+ plugins: [vuetify],
243
+ },
244
+ props: {
245
+ modelValue: null,
246
+ label: 'Date avec append icon',
247
+ displayAppendIcon: true,
248
+ },
249
+ })
250
+
251
+ await nextTick()
252
+
253
+ expect(wrapper.exists()).toBe(true)
254
+ })
255
+ })
256
+
257
+ describe('Messages de validation', () => {
258
+ it('doit afficher des messages d\'erreur', async () => {
259
+ wrapper = mount(DatePicker, {
260
+ global: {
261
+ plugins: [vuetify],
262
+ },
263
+ props: {
264
+ modelValue: null,
265
+ label: 'Date avec erreur',
266
+ errorMessages: ['Erreur de test'],
267
+ },
268
+ })
269
+
270
+ await nextTick()
271
+
272
+ expect(wrapper.exists()).toBe(true)
273
+ })
274
+
275
+ it('doit afficher des messages de succès', async () => {
276
+ wrapper = mount(DatePicker, {
277
+ global: {
278
+ plugins: [vuetify],
279
+ },
280
+ props: {
281
+ modelValue: '2024-06-15',
282
+ label: 'Date avec succès',
283
+ successMessages: ['Validation réussie'],
284
+ },
285
+ })
286
+
287
+ await nextTick()
288
+
289
+ expect(wrapper.exists()).toBe(true)
290
+ })
291
+
292
+ it('doit afficher des messages d\'avertissement', async () => {
293
+ wrapper = mount(DatePicker, {
294
+ global: {
295
+ plugins: [vuetify],
296
+ },
297
+ props: {
298
+ modelValue: '2024-06-15',
299
+ label: 'Date avec avertissement',
300
+ warningMessages: ['Avertissement de test'],
301
+ },
302
+ })
303
+
304
+ await nextTick()
305
+
306
+ expect(wrapper.exists()).toBe(true)
307
+ })
308
+ })
309
+
310
+ describe('Événements', () => {
311
+ it('doit émettre l\'événement focus', async () => {
312
+ wrapper = mount(DatePicker, {
313
+ global: {
314
+ plugins: [vuetify],
315
+ },
316
+ props: {
317
+ modelValue: null,
318
+ label: 'Date événement focus',
319
+ },
320
+ })
321
+
322
+ await nextTick()
323
+
324
+ const input = wrapper.find('input')
325
+ await input.trigger('focus')
326
+
327
+ expect(wrapper.emitted('focus')).toBeTruthy()
328
+ })
329
+
330
+ it('doit émettre l\'événement blur', async () => {
331
+ wrapper = mount(DatePicker, {
332
+ global: {
333
+ plugins: [vuetify],
334
+ },
335
+ props: {
336
+ modelValue: null,
337
+ label: 'Date événement blur',
338
+ },
339
+ })
340
+
341
+ await nextTick()
342
+
343
+ const input = wrapper.find('input')
344
+ await input.trigger('focus')
345
+ await input.trigger('blur')
346
+
347
+ expect(wrapper.emitted('blur')).toBeTruthy()
348
+ })
349
+ })
350
+
351
+ describe('Custom rules sur champs vides (Mémoire b5baeb0e)', () => {
352
+ it('doit exécuter les custom rules sur champ vide avec validateOnSubmit()', async () => {
353
+ const customRuleMock = vi.fn().mockReturnValue(false)
354
+ const customRules = [
355
+ {
356
+ type: 'custom',
357
+ options: {
358
+ validate: customRuleMock,
359
+ message: 'Champ obligatoire',
360
+ },
361
+ },
362
+ ]
363
+
364
+ wrapper = mount(DatePicker, {
365
+ global: {
366
+ plugins: [vuetify],
367
+ },
368
+ props: {
369
+ modelValue: null,
370
+ label: 'Date CalendarMode',
371
+ customRules,
372
+ },
373
+ })
374
+
375
+ await nextTick()
376
+
377
+ const result = wrapper.vm.validateOnSubmit()
378
+ expect(customRuleMock).toHaveBeenCalledWith(null)
379
+ expect(result).toBe(false)
380
+ })
381
+
382
+ it('doit exécuter les custom rules après interaction utilisateur', async () => {
383
+ const customRuleMock = vi.fn().mockReturnValue(false)
384
+ const customRules = [
385
+ {
386
+ type: 'custom',
387
+ options: {
388
+ validate: customRuleMock,
389
+ message: 'Erreur après interaction',
390
+ },
391
+ },
392
+ ]
393
+
394
+ wrapper = mount(DatePicker, {
395
+ global: {
396
+ plugins: [vuetify],
397
+ },
398
+ props: {
399
+ modelValue: null,
400
+ label: 'Date CalendarMode',
401
+ customRules,
402
+ },
403
+ })
404
+
405
+ await nextTick()
406
+
407
+ // Simuler interaction utilisateur
408
+ const input = wrapper.find('input')
409
+ await input.trigger('focus')
410
+ await input.trigger('blur')
411
+
412
+ expect(customRuleMock).toHaveBeenCalledWith(null)
413
+ })
414
+ })
415
+
416
+ describe('Validation avec required', () => {
417
+ it('doit afficher erreur required sur champ vide', async () => {
418
+ wrapper = mount(DatePicker, {
419
+ global: {
420
+ plugins: [vuetify],
421
+ },
422
+ props: {
423
+ modelValue: null,
424
+ label: 'Date CalendarMode',
425
+ required: true,
426
+ },
427
+ })
428
+
429
+ await nextTick()
430
+
431
+ // Simuler interaction
432
+ const input = wrapper.find('input')
433
+ await input.trigger('focus')
434
+ await input.trigger('blur')
435
+
436
+ const result = wrapper.vm.validateOnSubmit()
437
+ expect(result).toBe(false)
438
+ })
439
+ })
440
+ })
441
+
442
+ /**
443
+ * TESTS POUR COMPLEX DATEPICKER (MODE STANDARD)
444
+ */
445
+ describe('DatePicker - Mode Standard', () => {
446
+ describe('Cas normaux - Fonctionnement de base', () => {
447
+ it('doit afficher le composant avec les props par défaut', async () => {
448
+ wrapper = mount(DatePicker, {
449
+ global: {
450
+ plugins: [vuetify],
451
+ },
452
+ props: {
453
+ useCombinedMode: true,
454
+ modelValue: null,
455
+ label: 'Date DatePicker',
456
+ },
457
+ })
458
+
459
+ await nextTick()
460
+
461
+ expect(wrapper.exists()).toBe(true)
462
+ expect(wrapper.find('input').exists()).toBe(true)
463
+ })
464
+
465
+ it('doit gérer le mode combined', async () => {
466
+ wrapper = mount(DatePicker, {
467
+ global: {
468
+ plugins: [vuetify],
469
+ },
470
+ props: {
471
+ useCombinedMode: true,
472
+ modelValue: null,
473
+ label: 'Date Combined Mode',
474
+ },
475
+ })
476
+
477
+ await nextTick()
478
+
479
+ expect(wrapper.exists()).toBe(true)
480
+ })
481
+
482
+ it('doit gérer les plages de dates', async () => {
483
+ wrapper = mount(DatePicker, {
484
+ global: {
485
+ plugins: [vuetify],
486
+ },
487
+ props: {
488
+ useCombinedMode: true,
489
+ modelValue: null,
490
+ label: 'Plage de dates Complex',
491
+ displayRange: true,
492
+ },
493
+ })
494
+
495
+ await nextTick()
496
+
497
+ expect(wrapper.exists()).toBe(true)
498
+ })
499
+
500
+ it('doit gérer le formatage automatique', async () => {
501
+ wrapper = mount(DatePicker, {
502
+ global: {
503
+ plugins: [vuetify],
504
+ },
505
+ props: {
506
+ useCombinedMode: true,
507
+ modelValue: null,
508
+ label: 'Date auto format',
509
+ autoClamp: true,
510
+ },
511
+ })
512
+
513
+ await nextTick()
514
+
515
+ expect(wrapper.exists()).toBe(true)
516
+ })
517
+
518
+ it('doit gérer le mode readonly', async () => {
519
+ wrapper = mount(DatePicker, {
520
+ global: {
521
+ plugins: [vuetify],
522
+ },
523
+ props: {
524
+ useCombinedMode: true,
525
+ modelValue: '2024-06-15',
526
+ label: 'Date readonly',
527
+ readonly: true,
528
+ },
529
+ })
530
+
531
+ await nextTick()
532
+
533
+ expect(wrapper.exists()).toBe(true)
534
+ })
535
+
536
+ it('doit gérer les custom rules de succès', async () => {
537
+ wrapper = mount(DatePicker, {
538
+ global: {
539
+ plugins: [vuetify],
540
+ },
541
+ props: {
542
+ useCombinedMode: true,
543
+ modelValue: '2024-06-15',
544
+ label: 'Date avec succès',
545
+ },
546
+ })
547
+
548
+ await nextTick()
549
+
550
+ // Vérifier que le composant existe
551
+ expect(wrapper.exists()).toBe(true)
552
+ expect(wrapper.find('input').exists()).toBe(true)
553
+ })
554
+
555
+ it('doit gérer les custom warning rules', async () => {
556
+ wrapper = mount(DatePicker, {
557
+ global: {
558
+ plugins: [vuetify],
559
+ },
560
+ props: {
561
+ useCombinedMode: true,
562
+ modelValue: '2024-06-15',
563
+ label: 'Date avec warning',
564
+ customWarningRules: [],
565
+ },
566
+ })
567
+
568
+ await nextTick()
569
+
570
+ // Vérifier que le composant existe et gère les warning rules
571
+ expect(wrapper.exists()).toBe(true)
572
+ expect(wrapper.props('customWarningRules')).toEqual([])
573
+ })
574
+ })
575
+
576
+ describe('Validation avec règles réactives (Mémoire 2196b67a)', () => {
577
+ it('doit gérer les règles réactives correctement', async () => {
578
+ const dateA = ref('2024-06-15')
579
+ const customRuleMock = vi.fn().mockReturnValue(true)
580
+
581
+ const dateBRules = computed(() => [
582
+ {
583
+ type: 'custom',
584
+ options: {
585
+ validate: customRuleMock,
586
+ message: `Date doit être après ${dateA.value}`,
587
+ },
588
+ },
589
+ ])
590
+
591
+ wrapper = mount(DatePicker, {
592
+ global: {
593
+ plugins: [vuetify],
594
+ },
595
+ props: {
596
+ useCombinedMode: true,
597
+ modelValue: null,
598
+ label: 'Date B',
599
+ customRules: dateBRules.value,
600
+ },
601
+ })
602
+
603
+ await nextTick()
604
+
605
+ // Validation initiale
606
+ wrapper.vm.validateOnSubmit()
607
+ expect(customRuleMock).toHaveBeenCalled()
608
+
609
+ // Changer dateA et re-valider
610
+ dateA.value = '2024-07-01'
611
+ await wrapper.setProps({ customRules: dateBRules.value })
612
+ await nextTick()
613
+
614
+ wrapper.vm.validateOnSubmit()
615
+ expect(customRuleMock).toHaveBeenCalled()
616
+ })
617
+ })
618
+ })
619
+
620
+ /**
621
+ * TESTS POUR COMPLEX DATEPICKER (MODE COMBINÉ)
622
+ */
623
+ describe('DatePicker - Mode Combiné (useCombinedMode=true)', () => {
624
+ describe('Messages d\'erreur en combined-mode (Mémoire a60921cf)', () => {
625
+ it('doit maintenir la réactivité des règles en combined-mode', async () => {
626
+ const dateA = ref('2024-06-15')
627
+ const customRuleMock = vi.fn().mockReturnValue(true)
628
+
629
+ const dateBRules = computed(() => [
630
+ {
631
+ type: 'custom',
632
+ options: {
633
+ validate: customRuleMock,
634
+ message: `Date doit être après ${dateA.value}`,
635
+ },
636
+ },
637
+ ])
638
+
639
+ wrapper = mount(DatePicker, {
640
+ global: {
641
+ plugins: [vuetify],
642
+ },
643
+ props: {
644
+ useCombinedMode: true,
645
+ modelValue: null,
646
+ label: 'Date B Combined',
647
+ customRules: dateBRules.value,
648
+ },
649
+ })
650
+
651
+ await nextTick()
652
+
653
+ // Validation initiale
654
+ wrapper.vm.validateOnSubmit()
655
+ expect(customRuleMock).toHaveBeenCalled()
656
+
657
+ // Changer dateA et vérifier la réactivité
658
+ dateA.value = '2024-07-01'
659
+ await wrapper.setProps({ customRules: dateBRules.value })
660
+ await nextTick()
661
+
662
+ wrapper.vm.validateOnSubmit()
663
+ expect(customRuleMock).toHaveBeenCalled()
664
+ })
665
+ })
666
+ })
667
+
668
+ /**
669
+ * TESTS POUR DATETEXTINPUT
670
+ */
671
+ describe('DatePicker - Mode Saisie Texte', () => {
672
+ describe('Cas normaux - Fonctionnement de base', () => {
673
+ it('doit afficher le composant avec les props par défaut', async () => {
674
+ wrapper = mount(DatePicker, {
675
+ global: {
676
+ plugins: [vuetify],
677
+ },
678
+ props: {
679
+ noCalendar: true,
680
+ modelValue: null,
681
+ label: 'Date TextInput',
682
+ },
683
+ })
684
+
685
+ await nextTick()
686
+
687
+ expect(wrapper.exists()).toBe(true)
688
+ expect(wrapper.find('input').exists()).toBe(true)
689
+ })
690
+
691
+ it('doit formater automatiquement la saisie', async () => {
692
+ wrapper = mount(DatePicker, {
693
+ global: {
694
+ plugins: [vuetify],
695
+ },
696
+ props: {
697
+ noCalendar: true,
698
+ modelValue: null,
699
+ label: 'Date avec formatage',
700
+ format: 'DD/MM/YYYY',
701
+ },
702
+ })
703
+
704
+ await nextTick()
705
+
706
+ const input = wrapper.find('input')
707
+ await input.setValue('15062024')
708
+ await input.trigger('blur')
709
+
710
+ expect(wrapper.exists()).toBe(true)
711
+ })
712
+
713
+ it('doit gérer les différents formats', async () => {
714
+ const formats = ['DD/MM/YYYY', 'MM/DD/YYYY', 'YYYY-MM-DD']
715
+
716
+ for (const format of formats) {
717
+ wrapper = mount(DatePicker, {
718
+ global: {
719
+ plugins: [vuetify],
720
+ },
721
+ props: {
722
+ noCalendar: true,
723
+ modelValue: null,
724
+ label: `Date format ${format}`,
725
+ format,
726
+ },
727
+ })
728
+
729
+ await nextTick()
730
+ expect(wrapper.exists()).toBe(true)
731
+ wrapper.unmount()
732
+ }
733
+ })
734
+
735
+ it('doit gérer l\'état required', async () => {
736
+ wrapper = mount(DatePicker, {
737
+ global: {
738
+ plugins: [vuetify],
739
+ },
740
+ props: {
741
+ noCalendar: true,
742
+ modelValue: null,
743
+ label: 'Date obligatoire',
744
+ required: true,
745
+ },
746
+ })
747
+
748
+ await nextTick()
749
+
750
+ const result = wrapper.vm.validateOnSubmit()
751
+ expect(result).toBe(false)
752
+ })
753
+
754
+ it('doit gérer l\'état disabled', async () => {
755
+ wrapper = mount(DatePicker, {
756
+ global: {
757
+ plugins: [vuetify],
758
+ },
759
+ props: {
760
+ noCalendar: true,
761
+ modelValue: null,
762
+ label: 'Date désactivée',
763
+ disabled: true,
764
+ },
765
+ })
766
+
767
+ await nextTick()
768
+
769
+ const input = wrapper.find('input')
770
+ expect(input.attributes('disabled')).toBeDefined()
771
+ })
772
+
773
+ it('doit gérer les icônes append', async () => {
774
+ wrapper = mount(DatePicker, {
775
+ global: {
776
+ plugins: [vuetify],
777
+ },
778
+ props: {
779
+ noCalendar: true,
780
+ modelValue: null,
781
+ label: 'Date avec append icon',
782
+ displayAppendIcon: true,
783
+ },
784
+ })
785
+
786
+ await nextTick()
787
+
788
+ expect(wrapper.exists()).toBe(true)
789
+ })
790
+
791
+ it('doit gérer l\'autoClamp', async () => {
792
+ wrapper = mount(DatePicker, {
793
+ global: {
794
+ plugins: [vuetify],
795
+ },
796
+ props: {
797
+ noCalendar: true,
798
+ modelValue: null,
799
+ label: 'Date avec autoClamp',
800
+ autoClamp: true,
801
+ },
802
+ })
803
+
804
+ await nextTick()
805
+
806
+ expect(wrapper.exists()).toBe(true)
807
+ })
808
+
809
+ it('doit valider les formats de date invalides', async () => {
810
+ wrapper = mount(DatePicker, {
811
+ global: {
812
+ plugins: [vuetify],
813
+ },
814
+ props: {
815
+ noCalendar: true,
816
+ modelValue: null,
817
+ label: 'Date validation format',
818
+ required: true, // Ajouter required pour s'assurer qu'il y a une validation
819
+ },
820
+ })
821
+
822
+ await nextTick()
823
+
824
+ const input = wrapper.find('input')
825
+ await input.setValue('32/13/2024') // Date invalide
826
+ await input.trigger('blur')
827
+
828
+ const result = wrapper.vm.validateOnSubmit()
829
+ expect(result).toBe(false)
830
+ })
831
+
832
+ it('doit gérer les custom warning rules', async () => {
833
+ const warningRuleMock = vi.fn().mockReturnValue(false)
834
+ const customWarningRules = [
835
+ {
836
+ type: 'custom',
837
+ options: {
838
+ validate: warningRuleMock,
839
+ message: 'Avertissement TextInput',
840
+ },
841
+ },
842
+ ]
843
+
844
+ wrapper = mount(DatePicker, {
845
+ global: {
846
+ plugins: [vuetify],
847
+ },
848
+ props: {
849
+ noCalendar: true,
850
+ modelValue: '2024-06-15',
851
+ label: 'Date avec warning',
852
+ customWarningRules,
853
+ },
854
+ })
855
+
856
+ await nextTick()
857
+
858
+ // Simuler interaction pour déclencher la validation
859
+ const input = wrapper.find('input')
860
+ await input.trigger('focus')
861
+ await input.trigger('blur')
862
+ await nextTick()
863
+
864
+ wrapper.vm.validateOnSubmit()
865
+ expect(warningRuleMock).toHaveBeenCalled()
866
+ })
867
+
868
+ it('doit émettre les événements correctement', async () => {
869
+ wrapper = mount(DatePicker, {
870
+ global: {
871
+ plugins: [vuetify],
872
+ },
873
+ props: {
874
+ noCalendar: true,
875
+ modelValue: null,
876
+ label: 'Date événements',
877
+ },
878
+ })
879
+
880
+ await nextTick()
881
+
882
+ const input = wrapper.find('input')
883
+ await input.trigger('focus')
884
+ await input.trigger('blur')
885
+
886
+ expect(wrapper.emitted('focus')).toBeTruthy()
887
+ expect(wrapper.emitted('blur')).toBeTruthy()
888
+ })
889
+ })
890
+
891
+ describe('Custom rules sur champs vides', () => {
892
+ it('doit exécuter les custom rules sur champ vide même sans interaction', async () => {
893
+ const customRuleMock = vi.fn().mockReturnValue(false)
894
+ const customRules = [
895
+ {
896
+ type: 'custom',
897
+ options: {
898
+ validate: customRuleMock,
899
+ message: 'Champ obligatoire',
900
+ },
901
+ },
902
+ ]
903
+
904
+ wrapper = mount(DatePicker, {
905
+ global: {
906
+ plugins: [vuetify],
907
+ },
908
+ props: {
909
+ noCalendar: true,
910
+ modelValue: null,
911
+ label: 'Date TextInput',
912
+ customRules,
913
+ },
914
+ })
915
+
916
+ await nextTick()
917
+
918
+ // DatePicker exécute les custom rules même sans interaction
919
+ const result = wrapper.vm.validateOnSubmit()
920
+ expect(customRuleMock).toHaveBeenCalledWith(null)
921
+ expect(result).toBe(false)
922
+ })
923
+ })
924
+ })
925
+
926
+ /**
927
+ * TESTS SPÉCIFIQUES AUX COMPOSABLES ET FONCTIONNALITÉS AVANCÉES
928
+ */
929
+ describe('Tests des Composables et Fonctionnalités', () => {
930
+ describe('Gestion des périodes (period)', () => {
931
+ it('doit respecter les dates min/max définies', async () => {
932
+ const period = {
933
+ min: '2024-01-01',
934
+ max: '2024-12-31',
935
+ }
936
+
937
+ wrapper = mount(DatePicker, {
938
+ global: {
939
+ plugins: [vuetify],
940
+ },
941
+ props: {
942
+ modelValue: null,
943
+ label: 'Date avec période',
944
+ period,
945
+ },
946
+ })
947
+
948
+ await nextTick()
949
+
950
+ expect(wrapper.exists()).toBe(true)
951
+ expect(wrapper.props('period')).toEqual(period)
952
+ })
953
+ })
954
+
955
+ describe('Gestion des jours fériés et week-ends', () => {
956
+ it('doit gérer displayWeekendDays', async () => {
957
+ wrapper = mount(DatePicker, {
958
+ global: {
959
+ plugins: [vuetify],
960
+ },
961
+ props: {
962
+ modelValue: null,
963
+ label: 'Date avec week-ends',
964
+ displayWeekendDays: false,
965
+ },
966
+ })
967
+
968
+ await nextTick()
969
+
970
+ expect(wrapper.exists()).toBe(true)
971
+ expect(wrapper.props('displayWeekendDays')).toBe(false)
972
+ })
973
+
974
+ it('doit gérer displayHolidayDays', async () => {
975
+ wrapper = mount(DatePicker, {
976
+ global: {
977
+ plugins: [vuetify],
978
+ },
979
+ props: {
980
+ modelValue: null,
981
+ label: 'Date avec jours fériés',
982
+ displayHolidayDays: false,
983
+ },
984
+ })
985
+
986
+ await nextTick()
987
+
988
+ expect(wrapper.exists()).toBe(true)
989
+ expect(wrapper.props('displayHolidayDays')).toBe(false)
990
+ })
991
+ })
992
+
993
+ describe('Gestion du bouton Today', () => {
994
+ it('doit gérer displayTodayButton', async () => {
995
+ wrapper = mount(DatePicker, {
996
+ global: {
997
+ plugins: [vuetify],
998
+ },
999
+ props: {
1000
+ modelValue: null,
1001
+ label: 'Date avec bouton Today',
1002
+ displayTodayButton: false,
1003
+ },
1004
+ })
1005
+
1006
+ await nextTick()
1007
+
1008
+ expect(wrapper.exists()).toBe(true)
1009
+ expect(wrapper.props('displayTodayButton')).toBe(false)
1010
+ })
1011
+ })
1012
+
1013
+ describe('Gestion des numéros de semaine', () => {
1014
+ it('doit gérer showWeekNumber', async () => {
1015
+ wrapper = mount(DatePicker, {
1016
+ global: {
1017
+ plugins: [vuetify],
1018
+ },
1019
+ props: {
1020
+ modelValue: null,
1021
+ label: 'Date avec numéros de semaine',
1022
+ showWeekNumber: true,
1023
+ },
1024
+ })
1025
+
1026
+ await nextTick()
1027
+
1028
+ expect(wrapper.exists()).toBe(true)
1029
+ expect(wrapper.props('showWeekNumber')).toBe(true)
1030
+ })
1031
+ })
1032
+
1033
+ describe('Gestion du textFieldActivator', () => {
1034
+ it('doit gérer textFieldActivator pour DatePicker', async () => {
1035
+ wrapper = mount(DatePicker, {
1036
+ global: {
1037
+ plugins: [vuetify],
1038
+ },
1039
+ props: {
1040
+ useCombinedMode: true,
1041
+ modelValue: null,
1042
+ label: 'Date avec textFieldActivator',
1043
+ textFieldActivator: true,
1044
+ },
1045
+ })
1046
+
1047
+ await nextTick()
1048
+
1049
+ expect(wrapper.exists()).toBe(true)
1050
+ expect(wrapper.props('textFieldActivator')).toBe(true)
1051
+ })
1052
+ })
1053
+
1054
+ describe('Gestion de la densité', () => {
1055
+ it('doit gérer les différentes densités', async () => {
1056
+ const densities = ['default', 'comfortable', 'compact'] as const
1057
+
1058
+ for (const density of densities) {
1059
+ wrapper = mount(DatePicker, {
1060
+ global: {
1061
+ plugins: [vuetify],
1062
+ },
1063
+ props: {
1064
+ modelValue: null,
1065
+ label: `Date densité ${density}`,
1066
+ density,
1067
+ },
1068
+ })
1069
+
1070
+ await nextTick()
1071
+ expect(wrapper.exists()).toBe(true)
1072
+ expect(wrapper.props('density')).toBe(density)
1073
+ wrapper.unmount()
1074
+ }
1075
+ })
1076
+ })
1077
+
1078
+ describe('Gestion de hideDetails', () => {
1079
+ it('doit gérer hideDetails=true', async () => {
1080
+ wrapper = mount(DatePicker, {
1081
+ global: {
1082
+ plugins: [vuetify],
1083
+ },
1084
+ props: {
1085
+ modelValue: null,
1086
+ label: 'Date sans détails',
1087
+ hideDetails: true,
1088
+ },
1089
+ })
1090
+
1091
+ await nextTick()
1092
+
1093
+ expect(wrapper.exists()).toBe(true)
1094
+ expect(wrapper.props('hideDetails')).toBe(true)
1095
+ })
1096
+
1097
+ it('doit gérer hideDetails="auto"', async () => {
1098
+ wrapper = mount(DatePicker, {
1099
+ global: {
1100
+ plugins: [vuetify],
1101
+ },
1102
+ props: {
1103
+ modelValue: null,
1104
+ label: 'Date détails auto',
1105
+ hideDetails: 'auto',
1106
+ },
1107
+ })
1108
+
1109
+ await nextTick()
1110
+
1111
+ expect(wrapper.exists()).toBe(true)
1112
+ expect(wrapper.props('hideDetails')).toBe('auto')
1113
+ })
1114
+ })
1115
+
1116
+ describe('Gestion de l\'astérisque', () => {
1117
+ it('doit gérer displayAsterisk', async () => {
1118
+ wrapper = mount(DatePicker, {
1119
+ global: {
1120
+ plugins: [vuetify],
1121
+ },
1122
+ props: {
1123
+ modelValue: null,
1124
+ label: 'Date avec astérisque',
1125
+ displayAsterisk: true,
1126
+ required: true,
1127
+ },
1128
+ })
1129
+
1130
+ await nextTick()
1131
+
1132
+ expect(wrapper.exists()).toBe(true)
1133
+ expect(wrapper.props('displayAsterisk')).toBe(true)
1134
+ })
1135
+ })
1136
+
1137
+ describe('Gestion de isValidateOnBlur', () => {
1138
+ it('doit gérer isValidateOnBlur=false', async () => {
1139
+ wrapper = mount(DatePicker, {
1140
+ global: {
1141
+ plugins: [vuetify],
1142
+ },
1143
+ props: {
1144
+ modelValue: null,
1145
+ label: 'Date sans validation blur',
1146
+ isValidateOnBlur: false,
1147
+ },
1148
+ })
1149
+
1150
+ await nextTick()
1151
+
1152
+ expect(wrapper.exists()).toBe(true)
1153
+ expect(wrapper.props('isValidateOnBlur')).toBe(false)
1154
+ })
1155
+ })
1156
+ })
1157
+
1158
+ /**
1159
+ * TESTS DE RÉGRESSION SPÉCIFIQUES
1160
+ */
1161
+ describe('Tests de Régression Spécifiques', () => {
1162
+ describe('Suppression via croix (Mémoire 1f50fe1b)', () => {
1163
+ it('ne doit pas afficher "Date invalide" lors de suppression via croix - CalendarMode', async () => {
1164
+ wrapper = mount(DatePicker, {
1165
+ global: {
1166
+ plugins: [vuetify],
1167
+ },
1168
+ props: {
1169
+ modelValue: '2024-06-15',
1170
+ label: 'Date CalendarMode',
1171
+ customRules: [
1172
+ {
1173
+ type: 'notBeforeDate',
1174
+ options: {
1175
+ date: '2024-01-01',
1176
+ message: 'Date trop ancienne',
1177
+ },
1178
+ },
1179
+ ],
1180
+ },
1181
+ })
1182
+
1183
+ await nextTick()
1184
+
1185
+ // Simuler suppression via croix (modelValue devient null)
1186
+ await wrapper.setProps({ modelValue: null })
1187
+ await nextTick()
1188
+
1189
+ // Ne doit pas afficher "Date invalide"
1190
+ const errorMessages = wrapper.findAll('.v-messages__message')
1191
+ const hasDateInvalidError = errorMessages.some(msg =>
1192
+ msg.text().includes('Date invalide'),
1193
+ )
1194
+ expect(hasDateInvalidError).toBe(false)
1195
+ })
1196
+
1197
+ it('ne doit pas afficher "Date invalide" lors de suppression via croix - DatePicker', async () => {
1198
+ wrapper = mount(DatePicker, {
1199
+ global: {
1200
+ plugins: [vuetify],
1201
+ },
1202
+ props: {
1203
+ useCombinedMode: true,
1204
+ modelValue: '2024-06-15',
1205
+ label: 'Date DatePicker',
1206
+ customRules: [
1207
+ {
1208
+ type: 'notAfterDate',
1209
+ options: {
1210
+ date: '2024-12-31',
1211
+ message: 'Date trop récente',
1212
+ },
1213
+ },
1214
+ ],
1215
+ },
1216
+ })
1217
+
1218
+ await nextTick()
1219
+
1220
+ // Simuler suppression via croix (modelValue devient null)
1221
+ await wrapper.setProps({ modelValue: null })
1222
+ await nextTick()
1223
+
1224
+ // Ne doit pas afficher "Date invalide"
1225
+ const errorMessages = wrapper.findAll('.v-messages__message')
1226
+ const hasDateInvalidError = errorMessages.some(msg =>
1227
+ msg.text().includes('Date invalide'),
1228
+ )
1229
+ expect(hasDateInvalidError).toBe(false)
1230
+ })
1231
+ })
1232
+
1233
+ describe('Configuration de règle invalide (Mémoire 36a3ff57)', () => {
1234
+ it('ne doit pas afficher "Configuration de la règle invalide" - DatePicker', async () => {
1235
+ const dateA = ref('2024-06-15')
1236
+
1237
+ const dateBRules = computed(() => [
1238
+ {
1239
+ type: 'notBeforeDate',
1240
+ options: {
1241
+ date: dateA.value,
1242
+ message: `Date ne peut pas être avant le ${dateA.value}`,
1243
+ },
1244
+ },
1245
+ ])
1246
+
1247
+ wrapper = mount(DatePicker, {
1248
+ global: {
1249
+ plugins: [vuetify],
1250
+ },
1251
+ props: {
1252
+ useCombinedMode: true,
1253
+ modelValue: null,
1254
+ label: 'Date B',
1255
+ customRules: dateBRules.value,
1256
+ },
1257
+ })
1258
+
1259
+ await nextTick()
1260
+
1261
+ // Changer dateA pour déclencher une mise à jour des règles
1262
+ dateA.value = '2024-07-01'
1263
+ await wrapper.setProps({ customRules: dateBRules.value })
1264
+ await nextTick()
1265
+
1266
+ // Saisir une date et valider
1267
+ await wrapper.setProps({ modelValue: '2024-07-15' })
1268
+ await nextTick()
1269
+
1270
+ wrapper.vm.validateOnSubmit()
1271
+
1272
+ // Ne doit pas y avoir d'erreur "Configuration de la règle invalide"
1273
+ const errorMessages = wrapper.findAll('.v-messages__message')
1274
+ const hasConfigError = errorMessages.some(msg =>
1275
+ msg.text().includes('Configuration de la règle invalide'),
1276
+ )
1277
+ expect(hasConfigError).toBe(false)
1278
+ })
1279
+ })
1280
+
1281
+ describe('Priorité des messages (Mémoire 36e3ba09)', () => {
1282
+ it('doit prioriser les erreurs sur les succès - DatePicker', async () => {
1283
+ const customErrorRule = vi.fn().mockReturnValue(false)
1284
+ const customSuccessRule = vi.fn().mockReturnValue(true)
1285
+
1286
+ const customRules = [
1287
+ {
1288
+ type: 'custom',
1289
+ options: {
1290
+ validate: customErrorRule,
1291
+ message: 'Erreur critique',
1292
+ },
1293
+ },
1294
+ ]
1295
+
1296
+ const customSuccessRules = [
1297
+ {
1298
+ type: 'custom',
1299
+ options: {
1300
+ validate: customSuccessRule,
1301
+ message: 'Validation réussie',
1302
+ },
1303
+ },
1304
+ ]
1305
+
1306
+ wrapper = mount(DatePicker, {
1307
+ global: {
1308
+ plugins: [vuetify],
1309
+ },
1310
+ props: {
1311
+ useCombinedMode: true,
1312
+ modelValue: '2024-06-15',
1313
+ label: 'Date DatePicker',
1314
+ customRules,
1315
+ customSuccessRules,
1316
+ },
1317
+ })
1318
+
1319
+ await nextTick()
1320
+
1321
+ // Simuler interaction pour déclencher la validation
1322
+ const input = wrapper.find('input')
1323
+ await input.trigger('focus')
1324
+ await input.trigger('blur')
1325
+ await nextTick()
1326
+
1327
+ const result = wrapper.vm.validateOnSubmit()
1328
+ expect(result).toBe(false) // Erreur doit primer
1329
+
1330
+ // Vérifier que les règles ont été appelées
1331
+ expect(customErrorRule).toHaveBeenCalled()
1332
+ })
1333
+ })
1334
+ })
1335
+
1336
+ /**
1337
+ * TESTS DE PERFORMANCE ET EDGE CASES
1338
+ */
1339
+ describe('Tests de Performance et Edge Cases', () => {
1340
+ describe('Gestion des valeurs limites', () => {
1341
+ it('doit gérer les dates très anciennes', async () => {
1342
+ const ancientDate = '1900-01-01'
1343
+ wrapper = mount(DatePicker, {
1344
+ global: {
1345
+ plugins: [vuetify],
1346
+ },
1347
+ props: {
1348
+ modelValue: ancientDate,
1349
+ label: 'Date ancienne',
1350
+ },
1351
+ })
1352
+
1353
+ await nextTick()
1354
+
1355
+ expect(wrapper.exists()).toBe(true)
1356
+ expect(wrapper.props('modelValue')).toBe(ancientDate)
1357
+ })
1358
+
1359
+ it('doit gérer les dates très futures', async () => {
1360
+ const futureDate = '2100-12-31'
1361
+ wrapper = mount(DatePicker, {
1362
+ global: {
1363
+ plugins: [vuetify],
1364
+ },
1365
+ props: {
1366
+ modelValue: futureDate,
1367
+ label: 'Date future',
1368
+ },
1369
+ })
1370
+
1371
+ await nextTick()
1372
+
1373
+ expect(wrapper.exists()).toBe(true)
1374
+ expect(wrapper.props('modelValue')).toBe(futureDate)
1375
+ })
1376
+ })
1377
+
1378
+ describe('Gestion des formats exotiques', () => {
1379
+ it('doit gérer le format YYYY/MM/DD', async () => {
1380
+ wrapper = mount(DatePicker, {
1381
+ global: {
1382
+ plugins: [vuetify],
1383
+ },
1384
+ props: {
1385
+ modelValue: null,
1386
+ label: 'Date format YYYY/MM/DD',
1387
+ format: 'YYYY/MM/DD',
1388
+ },
1389
+ })
1390
+
1391
+ await nextTick()
1392
+
1393
+ expect(wrapper.exists()).toBe(true)
1394
+ expect(wrapper.props('format')).toBe('YYYY/MM/DD')
1395
+ })
1396
+
1397
+ it('doit gérer le format DD-MM-YYYY', async () => {
1398
+ wrapper = mount(DatePicker, {
1399
+ global: {
1400
+ plugins: [vuetify],
1401
+ },
1402
+ props: {
1403
+ modelValue: null,
1404
+ label: 'Date format DD-MM-YYYY',
1405
+ format: 'DD-MM-YYYY',
1406
+ },
1407
+ })
1408
+
1409
+ await nextTick()
1410
+
1411
+ expect(wrapper.exists()).toBe(true)
1412
+ expect(wrapper.props('format')).toBe('DD-MM-YYYY')
1413
+ })
1414
+ })
1415
+
1416
+ describe('Gestion des plages de dates complexes', () => {
1417
+ it('doit gérer les plages de dates avec DatePicker', async () => {
1418
+ wrapper = mount(DatePicker, {
1419
+ global: {
1420
+ plugins: [vuetify],
1421
+ },
1422
+ props: {
1423
+ modelValue: null,
1424
+ label: 'Plage DatePicker',
1425
+ displayRange: true,
1426
+ },
1427
+ })
1428
+
1429
+ await nextTick()
1430
+
1431
+ expect(wrapper.exists()).toBe(true)
1432
+ expect(wrapper.props('displayRange')).toBe(true)
1433
+ })
1434
+
1435
+ it('doit gérer les plages de dates avec des valeurs initiales', async () => {
1436
+ const rangeValue = ['2024-06-01', '2024-06-30']
1437
+ wrapper = mount(DatePicker, {
1438
+ global: {
1439
+ plugins: [vuetify],
1440
+ },
1441
+ props: {
1442
+ useCombinedMode: true,
1443
+ modelValue: rangeValue,
1444
+ label: 'Plage avec valeurs',
1445
+ displayRange: true,
1446
+ },
1447
+ })
1448
+
1449
+ await nextTick()
1450
+
1451
+ expect(wrapper.exists()).toBe(true)
1452
+ expect(wrapper.props('modelValue')).toEqual(rangeValue)
1453
+ })
1454
+ })
1455
+
1456
+ describe('Tests avec de nombreuses règles', () => {
1457
+ it('doit gérer de nombreuses custom rules', async () => {
1458
+ const manyRules = Array.from({ length: 10 }, (_, i) => ({
1459
+ type: 'custom',
1460
+ options: {
1461
+ validate: vi.fn().mockReturnValue(true),
1462
+ message: `Règle ${i + 1}`,
1463
+ },
1464
+ }))
1465
+
1466
+ wrapper = mount(DatePicker, {
1467
+ global: {
1468
+ plugins: [vuetify],
1469
+ },
1470
+ props: {
1471
+ useCombinedMode: true,
1472
+ modelValue: '2024-06-15',
1473
+ label: 'Date avec nombreuses règles',
1474
+ customRules: manyRules,
1475
+ },
1476
+ })
1477
+
1478
+ await nextTick()
1479
+
1480
+ const result = wrapper.vm.validateOnSubmit()
1481
+ expect(result).toBe(true)
1482
+
1483
+ // Vérifier que toutes les règles ont été appelées
1484
+ manyRules.forEach((rule) => {
1485
+ expect(rule.options.validate).toHaveBeenCalled()
1486
+ })
1487
+ })
1488
+ })
1489
+ })
1490
+
1491
+ /**
1492
+ * TESTS MANQUANTS IDENTIFIÉS - COUVERTURE COMPLÈTE
1493
+ */
1494
+ describe('Tests Manquants - Couverture Complète', () => {
1495
+ describe('Tests du prop useCombinedMode', () => {
1496
+ it('doit gérer useCombinedMode=true avec CalendarMode', async () => {
1497
+ wrapper = mount(DatePicker, {
1498
+ global: {
1499
+ plugins: [vuetify],
1500
+ },
1501
+ props: {
1502
+ modelValue: null,
1503
+ label: 'Date Combined Mode',
1504
+ useCombinedMode: true,
1505
+ },
1506
+ })
1507
+
1508
+ await nextTick()
1509
+
1510
+ expect(wrapper.exists()).toBe(true)
1511
+ expect(wrapper.props('useCombinedMode')).toBe(true)
1512
+ })
1513
+
1514
+ it('doit déléguer la validation au DatePicker en mode combiné', async () => {
1515
+ wrapper = mount(DatePicker, {
1516
+ global: {
1517
+ plugins: [vuetify],
1518
+ },
1519
+ props: {
1520
+ modelValue: null,
1521
+ label: 'Date Combined Mode',
1522
+ useCombinedMode: true,
1523
+ required: true,
1524
+ },
1525
+ })
1526
+
1527
+ await nextTick()
1528
+
1529
+ // En mode combiné, la validation doit être déléguée
1530
+ const result = wrapper.vm.validateOnSubmit()
1531
+ expect(typeof result).toBe('boolean')
1532
+ })
1533
+ })
1534
+
1535
+ describe('Tests du prop noCalendar', () => {
1536
+ it('doit gérer noCalendar=true avec CalendarMode', async () => {
1537
+ wrapper = mount(DatePicker, {
1538
+ global: {
1539
+ plugins: [vuetify],
1540
+ },
1541
+ props: {
1542
+ modelValue: null,
1543
+ label: 'Date sans calendrier',
1544
+ noCalendar: true,
1545
+ },
1546
+ })
1547
+
1548
+ await nextTick()
1549
+
1550
+ expect(wrapper.exists()).toBe(true)
1551
+ expect(wrapper.props('noCalendar')).toBe(true)
1552
+ })
1553
+
1554
+ it('doit gérer noCalendar=true avec DatePicker', async () => {
1555
+ wrapper = mount(DatePicker, {
1556
+ global: {
1557
+ plugins: [vuetify],
1558
+ },
1559
+ props: {
1560
+ useCombinedMode: true,
1561
+ modelValue: null,
1562
+ label: 'Date sans calendrier Complex',
1563
+ noCalendar: true,
1564
+ },
1565
+ })
1566
+
1567
+ await nextTick()
1568
+
1569
+ expect(wrapper.exists()).toBe(true)
1570
+ expect(wrapper.props('noCalendar')).toBe(true)
1571
+ })
1572
+
1573
+ it('doit déléguer la validation au DatePicker en mode noCalendar', async () => {
1574
+ wrapper = mount(DatePicker, {
1575
+ global: {
1576
+ plugins: [vuetify],
1577
+ },
1578
+ props: {
1579
+ useCombinedMode: true,
1580
+ modelValue: null,
1581
+ label: 'Date noCalendar',
1582
+ noCalendar: true,
1583
+ required: true,
1584
+ },
1585
+ })
1586
+
1587
+ await nextTick()
1588
+
1589
+ // En mode noCalendar, la validation doit être déléguée au DatePicker
1590
+ const result = wrapper.vm.validateOnSubmit()
1591
+ expect(typeof result).toBe('boolean')
1592
+ })
1593
+ })
1594
+
1595
+ describe('Tests du prop isBirthDate vs birthDate', () => {
1596
+ it('doit gérer isBirthDate=true avec CalendarMode', async () => {
1597
+ wrapper = mount(DatePicker, {
1598
+ global: {
1599
+ plugins: [vuetify],
1600
+ },
1601
+ props: {
1602
+ modelValue: null,
1603
+ label: 'Date de naissance isBirthDate',
1604
+ isBirthDate: true,
1605
+ },
1606
+ })
1607
+
1608
+ await nextTick()
1609
+
1610
+ expect(wrapper.exists()).toBe(true)
1611
+ expect(wrapper.props('isBirthDate')).toBe(true)
1612
+ })
1613
+
1614
+ it('doit gérer birthDate=true (alias) avec CalendarMode', async () => {
1615
+ wrapper = mount(DatePicker, {
1616
+ global: {
1617
+ plugins: [vuetify],
1618
+ },
1619
+ props: {
1620
+ modelValue: null,
1621
+ label: 'Date de naissance birthDate',
1622
+ birthDate: true,
1623
+ },
1624
+ })
1625
+
1626
+ await nextTick()
1627
+
1628
+ expect(wrapper.exists()).toBe(true)
1629
+ expect(wrapper.props('birthDate')).toBe(true)
1630
+ })
1631
+
1632
+ it('doit gérer isBirthDate=true avec DatePicker', async () => {
1633
+ wrapper = mount(DatePicker, {
1634
+ global: {
1635
+ plugins: [vuetify],
1636
+ },
1637
+ props: {
1638
+ useCombinedMode: true,
1639
+ modelValue: null,
1640
+ label: 'Date de naissance Complex',
1641
+ isBirthDate: true,
1642
+ },
1643
+ })
1644
+
1645
+ await nextTick()
1646
+
1647
+ expect(wrapper.exists()).toBe(true)
1648
+ expect(wrapper.props('isBirthDate')).toBe(true)
1649
+ })
1650
+ })
1651
+
1652
+ describe('Tests du prop dateFormatReturn', () => {
1653
+ it('doit gérer dateFormatReturn différent du format d\'affichage - CalendarMode', async () => {
1654
+ wrapper = mount(DatePicker, {
1655
+ global: {
1656
+ plugins: [vuetify],
1657
+ },
1658
+ props: {
1659
+ modelValue: null,
1660
+ label: 'Date avec format retour',
1661
+ format: 'DD/MM/YYYY',
1662
+ dateFormatReturn: 'YYYY-MM-DD',
1663
+ },
1664
+ })
1665
+
1666
+ await nextTick()
1667
+
1668
+ expect(wrapper.exists()).toBe(true)
1669
+ expect(wrapper.props('format')).toBe('DD/MM/YYYY')
1670
+ expect(wrapper.props('dateFormatReturn')).toBe('YYYY-MM-DD')
1671
+ })
1672
+
1673
+ it('doit gérer dateFormatReturn différent du format d\'affichage - DatePicker', async () => {
1674
+ wrapper = mount(DatePicker, {
1675
+ global: {
1676
+ plugins: [vuetify],
1677
+ },
1678
+ props: {
1679
+ useCombinedMode: true,
1680
+ modelValue: null,
1681
+ label: 'Date avec format retour Complex',
1682
+ format: 'DD/MM/YYYY',
1683
+ dateFormatReturn: 'YYYY-MM-DD',
1684
+ },
1685
+ })
1686
+
1687
+ await nextTick()
1688
+
1689
+ expect(wrapper.exists()).toBe(true)
1690
+ expect(wrapper.props('format')).toBe('DD/MM/YYYY')
1691
+ expect(wrapper.props('dateFormatReturn')).toBe('YYYY-MM-DD')
1692
+ })
1693
+
1694
+ it('doit gérer dateFormatReturn différent du format d\'affichage - DatePicker', async () => {
1695
+ wrapper = mount(DatePicker, {
1696
+ global: {
1697
+ plugins: [vuetify],
1698
+ },
1699
+ props: {
1700
+ modelValue: null,
1701
+ label: 'Date avec format retour TextInput',
1702
+ format: 'DD/MM/YYYY',
1703
+ dateFormatReturn: 'YYYY-MM-DD',
1704
+ },
1705
+ })
1706
+
1707
+ await nextTick()
1708
+
1709
+ expect(wrapper.exists()).toBe(true)
1710
+ expect(wrapper.props('format')).toBe('DD/MM/YYYY')
1711
+ expect(wrapper.props('dateFormatReturn')).toBe('YYYY-MM-DD')
1712
+ })
1713
+ })
1714
+
1715
+ describe('Tests des props de style et apparence', () => {
1716
+ it('doit gérer isOutlined=false avec CalendarMode', async () => {
1717
+ wrapper = mount(DatePicker, {
1718
+ global: {
1719
+ plugins: [vuetify],
1720
+ },
1721
+ props: {
1722
+ modelValue: null,
1723
+ label: 'Date non outlined',
1724
+ isOutlined: false,
1725
+ },
1726
+ })
1727
+
1728
+ await nextTick()
1729
+
1730
+ expect(wrapper.exists()).toBe(true)
1731
+ expect(wrapper.props('isOutlined')).toBe(false)
1732
+ })
1733
+
1734
+ it('doit gérer bgColor personnalisé', async () => {
1735
+ wrapper = mount(DatePicker, {
1736
+ global: {
1737
+ plugins: [vuetify],
1738
+ },
1739
+ props: {
1740
+ useCombinedMode: true,
1741
+ modelValue: null,
1742
+ label: 'Date avec couleur',
1743
+ bgColor: 'lightblue',
1744
+ },
1745
+ })
1746
+
1747
+ await nextTick()
1748
+
1749
+ expect(wrapper.exists()).toBe(true)
1750
+ expect(wrapper.props('bgColor')).toBe('lightblue')
1751
+ })
1752
+
1753
+ it('doit gérer width personnalisé', async () => {
1754
+ wrapper = mount(DatePicker, {
1755
+ global: {
1756
+ plugins: [vuetify],
1757
+ },
1758
+ props: {
1759
+ modelValue: null,
1760
+ label: 'Date avec largeur',
1761
+ width: '300px',
1762
+ },
1763
+ })
1764
+
1765
+ await nextTick()
1766
+
1767
+ expect(wrapper.exists()).toBe(true)
1768
+ expect(wrapper.props('width')).toBe('300px')
1769
+ })
1770
+ })
1771
+
1772
+ describe('Tests des événements', () => {
1773
+ it('doit émettre l\'événement closed - CalendarMode', async () => {
1774
+ wrapper = mount(DatePicker, {
1775
+ global: {
1776
+ plugins: [vuetify],
1777
+ },
1778
+ props: {
1779
+ modelValue: null,
1780
+ label: 'Date événement closed',
1781
+ },
1782
+ })
1783
+
1784
+ await nextTick()
1785
+
1786
+ // Vérifier que le composant existe et peut émettre des événements
1787
+ expect(wrapper.exists()).toBe(true)
1788
+ expect(wrapper.vm.isDatePickerVisible).toBeDefined()
1789
+ })
1790
+
1791
+ it('doit émettre l\'événement input - DatePicker', async () => {
1792
+ wrapper = mount(DatePicker, {
1793
+ global: {
1794
+ plugins: [vuetify],
1795
+ },
1796
+ props: {
1797
+ useCombinedMode: true,
1798
+ modelValue: null,
1799
+ label: 'Date événement input',
1800
+ },
1801
+ })
1802
+
1803
+ await nextTick()
1804
+
1805
+ // Vérifier que le composant existe et peut gérer les inputs
1806
+ const input = wrapper.find('input')
1807
+ expect(input.exists()).toBe(true)
1808
+ expect(wrapper.exists()).toBe(true)
1809
+ })
1810
+
1811
+ it('doit émettre l\'événement date-selected - DatePicker', async () => {
1812
+ wrapper = mount(DatePicker, {
1813
+ global: {
1814
+ plugins: [vuetify],
1815
+ },
1816
+ props: {
1817
+ modelValue: null,
1818
+ label: 'Date événement date-selected',
1819
+ format: 'DD/MM/YYYY',
1820
+ },
1821
+ })
1822
+
1823
+ await nextTick()
1824
+
1825
+ const input = wrapper.find('input')
1826
+ await input.setValue('15/06/2024')
1827
+ await input.trigger('blur')
1828
+
1829
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy()
1830
+ })
1831
+ })
1832
+
1833
+ describe('Tests des props de validation avancée', () => {
1834
+ it('doit gérer disableErrorHandling=true', async () => {
1835
+ wrapper = mount(DatePicker, {
1836
+ global: {
1837
+ plugins: [vuetify],
1838
+ },
1839
+ props: {
1840
+ modelValue: null,
1841
+ label: 'Date sans gestion erreur',
1842
+ disableErrorHandling: true,
1843
+ required: true,
1844
+ },
1845
+ })
1846
+
1847
+ await nextTick()
1848
+
1849
+ expect(wrapper.exists()).toBe(true)
1850
+ expect(wrapper.props('disableErrorHandling')).toBe(true)
1851
+ })
1852
+
1853
+ it('doit gérer showSuccessMessages=false', async () => {
1854
+ wrapper = mount(DatePicker, {
1855
+ global: {
1856
+ plugins: [vuetify],
1857
+ },
1858
+ props: {
1859
+ useCombinedMode: true,
1860
+ modelValue: '2024-06-15',
1861
+ label: 'Date sans messages succès',
1862
+ showSuccessMessages: false,
1863
+ },
1864
+ })
1865
+
1866
+ await nextTick()
1867
+
1868
+ expect(wrapper.exists()).toBe(true)
1869
+ expect(wrapper.props('showSuccessMessages')).toBe(false)
1870
+ })
1871
+ })
1872
+
1873
+ describe('Tests des méthodes exposées', () => {
1874
+ it('doit exposer isDatePickerVisible - CalendarMode', async () => {
1875
+ wrapper = mount(DatePicker, {
1876
+ global: {
1877
+ plugins: [vuetify],
1878
+ },
1879
+ props: {
1880
+ modelValue: null,
1881
+ label: 'Date méthodes exposées',
1882
+ },
1883
+ })
1884
+
1885
+ await nextTick()
1886
+
1887
+ expect(wrapper.vm.isDatePickerVisible).toBeDefined()
1888
+ expect(typeof wrapper.vm.isDatePickerVisible).toBe('boolean')
1889
+ })
1890
+
1891
+ it('doit exposer selectedDates - DatePicker', async () => {
1892
+ wrapper = mount(DatePicker, {
1893
+ global: {
1894
+ plugins: [vuetify],
1895
+ },
1896
+ props: {
1897
+ useCombinedMode: true,
1898
+ modelValue: '2024-06-15',
1899
+ label: 'Date selectedDates exposé',
1900
+ },
1901
+ })
1902
+
1903
+ await nextTick()
1904
+
1905
+ expect(wrapper.vm.selectedDates).toBeDefined()
1906
+ })
1907
+
1908
+ it('doit émettre focus lors du focus sur DatePicker', async () => {
1909
+ wrapper = mount(DatePicker, {
1910
+ global: {
1911
+ plugins: [vuetify],
1912
+ },
1913
+ props: {
1914
+ noCalendar: true,
1915
+ modelValue: null,
1916
+ label: 'Date focus exposé',
1917
+ },
1918
+ })
1919
+
1920
+ await nextTick()
1921
+
1922
+ const input = wrapper.find('input')
1923
+ await input.trigger('focus')
1924
+
1925
+ expect(wrapper.emitted('focus')).toBeTruthy()
1926
+ })
1927
+ })
1928
+
1929
+ describe('Tests des interactions complexes', () => {
1930
+ it('doit gérer le changement de format à la volée', async () => {
1931
+ wrapper = mount(DatePicker, {
1932
+ global: {
1933
+ plugins: [vuetify],
1934
+ },
1935
+ props: {
1936
+ modelValue: null,
1937
+ label: 'Date changement format',
1938
+ format: 'DD/MM/YYYY',
1939
+ },
1940
+ })
1941
+
1942
+ await nextTick()
1943
+
1944
+ // Changer le format
1945
+ await wrapper.setProps({ format: 'MM/DD/YYYY' })
1946
+ await nextTick()
1947
+
1948
+ expect(wrapper.props('format')).toBe('MM/DD/YYYY')
1949
+ })
1950
+
1951
+ it('doit gérer les changements de règles dynamiques', async () => {
1952
+ const initialRules = [
1953
+ {
1954
+ type: 'notBeforeDate',
1955
+ options: {
1956
+ date: '2024-01-01',
1957
+ message: 'Date trop ancienne',
1958
+ },
1959
+ },
1960
+ ]
1961
+
1962
+ const newRules = [
1963
+ {
1964
+ type: 'notAfterDate',
1965
+ options: {
1966
+ date: '2024-12-31',
1967
+ message: 'Date trop récente',
1968
+ },
1969
+ },
1970
+ ]
1971
+
1972
+ wrapper = mount(DatePicker, {
1973
+ global: {
1974
+ plugins: [vuetify],
1975
+ },
1976
+ props: {
1977
+ useCombinedMode: true,
1978
+ modelValue: '2024-06-15',
1979
+ label: 'Date règles dynamiques',
1980
+ customRules: initialRules,
1981
+ },
1982
+ })
1983
+
1984
+ await nextTick()
1985
+
1986
+ // Changer les règles
1987
+ await wrapper.setProps({ customRules: newRules })
1988
+ await nextTick()
1989
+
1990
+ const result = wrapper.vm.validateOnSubmit()
1991
+ expect(typeof result).toBe('boolean')
1992
+ })
1993
+
1994
+ it('doit gérer les plages de dates avec des valeurs partielles', async () => {
1995
+ wrapper = mount(DatePicker, {
1996
+ global: {
1997
+ plugins: [vuetify],
1998
+ },
1999
+ props: {
2000
+ useCombinedMode: true,
2001
+ modelValue: ['2024-06-01', ''],
2002
+ label: 'Plage partielle',
2003
+ displayRange: true,
2004
+ },
2005
+ })
2006
+
2007
+ await nextTick()
2008
+
2009
+ expect(wrapper.exists()).toBe(true)
2010
+ expect(Array.isArray(wrapper.props('modelValue'))).toBe(true)
2011
+ })
2012
+ })
2013
+
2014
+ describe('Tests de robustesse et edge cases', () => {
2015
+ it('doit gérer les valeurs modelValue invalides', async () => {
2016
+ wrapper = mount(DatePicker, {
2017
+ global: {
2018
+ plugins: [vuetify],
2019
+ },
2020
+ props: {
2021
+ modelValue: 'invalid-date',
2022
+ label: 'Date invalide',
2023
+ },
2024
+ })
2025
+
2026
+ await nextTick()
2027
+
2028
+ expect(wrapper.exists()).toBe(true)
2029
+ const result = wrapper.vm.validateOnSubmit()
2030
+ expect(typeof result).toBe('boolean')
2031
+ })
2032
+
2033
+ it('doit gérer les props undefined/null', async () => {
2034
+ wrapper = mount(DatePicker, {
2035
+ global: {
2036
+ plugins: [vuetify],
2037
+ },
2038
+ props: {
2039
+ modelValue: null,
2040
+ label: undefined,
2041
+ placeholder: undefined,
2042
+ },
2043
+ })
2044
+
2045
+ await nextTick()
2046
+
2047
+ expect(wrapper.exists()).toBe(true)
2048
+ })
2049
+
2050
+ it('doit gérer les custom rules avec des fonctions qui lèvent des erreurs', async () => {
2051
+ const errorRule = vi.fn().mockReturnValue(false) // Simuler une règle qui échoue sans lever d'erreur
2052
+
2053
+ wrapper = mount(DatePicker, {
2054
+ global: {
2055
+ plugins: [vuetify],
2056
+ },
2057
+ props: {
2058
+ useCombinedMode: true,
2059
+ modelValue: '2024-06-15',
2060
+ label: 'Date règle erreur',
2061
+ customRules: [
2062
+ {
2063
+ type: 'custom',
2064
+ options: {
2065
+ validate: errorRule,
2066
+ message: 'Règle avec erreur',
2067
+ },
2068
+ },
2069
+ ],
2070
+ },
2071
+ })
2072
+
2073
+ await nextTick()
2074
+
2075
+ // La validation doit fonctionner même avec des règles qui échouent
2076
+ const result = wrapper.vm.validateOnSubmit()
2077
+ expect(typeof result).toBe('boolean')
2078
+ expect(errorRule).toHaveBeenCalled()
2079
+ })
2080
+
2081
+ it('doit gérer les changements rapides de modelValue', async () => {
2082
+ wrapper = mount(DatePicker, {
2083
+ global: {
2084
+ plugins: [vuetify],
2085
+ },
2086
+ props: {
2087
+ modelValue: null,
2088
+ label: 'Date changements rapides',
2089
+ },
2090
+ })
2091
+
2092
+ await nextTick()
2093
+
2094
+ // Changements rapides
2095
+ await wrapper.setProps({ modelValue: '2024-01-01' })
2096
+ await wrapper.setProps({ modelValue: '2024-02-02' })
2097
+ await wrapper.setProps({ modelValue: '2024-03-03' })
2098
+ await nextTick()
2099
+
2100
+ expect(wrapper.exists()).toBe(true)
2101
+ expect(wrapper.props('modelValue')).toBe('2024-03-03')
2102
+ })
2103
+ })
2104
+ })
2105
+
2106
+ /**
2107
+ * TESTS COMPLETS DES RÈGLES useFieldValidation - TOUS LES MODES
2108
+ */
2109
+ describe('Tests Complets des Règles useFieldValidation', () => {
2110
+ describe('Tests des règles numériques', () => {
2111
+ it('doit gérer la règle min - CalendarMode', async () => {
2112
+ wrapper = mount(DatePicker, {
2113
+ global: {
2114
+ plugins: [vuetify],
2115
+ },
2116
+ props: {
2117
+ modelValue: null,
2118
+ label: 'Date avec règle min',
2119
+ customRules: [
2120
+ {
2121
+ type: 'min',
2122
+ options: {
2123
+ value: 5,
2124
+ message: 'La valeur doit être supérieure à 5',
2125
+ },
2126
+ },
2127
+ ],
2128
+ },
2129
+ })
2130
+
2131
+ await nextTick()
2132
+
2133
+ expect(wrapper.exists()).toBe(true)
2134
+ const result = wrapper.vm.validateOnSubmit()
2135
+ expect(typeof result).toBe('boolean')
2136
+ })
2137
+
2138
+ it('doit gérer la règle max - DatePicker', async () => {
2139
+ wrapper = mount(DatePicker, {
2140
+ global: {
2141
+ plugins: [vuetify],
2142
+ },
2143
+ props: {
2144
+ useCombinedMode: true,
2145
+ modelValue: null,
2146
+ label: 'Date avec règle max',
2147
+ customRules: [
2148
+ {
2149
+ type: 'max',
2150
+ options: {
2151
+ value: 100,
2152
+ message: 'La valeur doit être inférieure à 100',
2153
+ },
2154
+ },
2155
+ ],
2156
+ },
2157
+ })
2158
+
2159
+ await nextTick()
2160
+
2161
+ expect(wrapper.exists()).toBe(true)
2162
+ const result = wrapper.vm.validateOnSubmit()
2163
+ expect(typeof result).toBe('boolean')
2164
+ })
2165
+ })
2166
+
2167
+ describe('Tests des règles de longueur', () => {
2168
+ it('doit gérer la règle minLength - DatePicker', async () => {
2169
+ wrapper = mount(DatePicker, {
2170
+ global: {
2171
+ plugins: [vuetify],
2172
+ },
2173
+ props: {
2174
+ modelValue: null,
2175
+ label: 'Date avec minLength',
2176
+ customRules: [
2177
+ {
2178
+ type: 'minLength',
2179
+ options: {
2180
+ length: 3,
2181
+ message: 'Minimum 3 caractères requis',
2182
+ },
2183
+ },
2184
+ ],
2185
+ },
2186
+ })
2187
+
2188
+ await nextTick()
2189
+
2190
+ expect(wrapper.exists()).toBe(true)
2191
+ const result = wrapper.vm.validateOnSubmit()
2192
+ expect(typeof result).toBe('boolean')
2193
+ })
2194
+
2195
+ it('doit gérer la règle maxLength - CalendarMode', async () => {
2196
+ wrapper = mount(DatePicker, {
2197
+ global: {
2198
+ plugins: [vuetify],
2199
+ },
2200
+ props: {
2201
+ modelValue: null,
2202
+ label: 'Date avec maxLength',
2203
+ customRules: [
2204
+ {
2205
+ type: 'maxLength',
2206
+ options: {
2207
+ length: 20,
2208
+ message: 'Maximum 20 caractères autorisés',
2209
+ },
2210
+ },
2211
+ ],
2212
+ },
2213
+ })
2214
+
2215
+ await nextTick()
2216
+
2217
+ expect(wrapper.exists()).toBe(true)
2218
+ const result = wrapper.vm.validateOnSubmit()
2219
+ expect(typeof result).toBe('boolean')
2220
+ })
2221
+
2222
+ it('doit gérer la règle exactLength - DatePicker', async () => {
2223
+ wrapper = mount(DatePicker, {
2224
+ global: {
2225
+ plugins: [vuetify],
2226
+ },
2227
+ props: {
2228
+ useCombinedMode: true,
2229
+ modelValue: null,
2230
+ label: 'Date avec exactLength',
2231
+ customRules: [
2232
+ {
2233
+ type: 'exactLength',
2234
+ options: {
2235
+ length: 10,
2236
+ message: 'Exactement 10 caractères requis',
2237
+ },
2238
+ },
2239
+ ],
2240
+ },
2241
+ })
2242
+
2243
+ await nextTick()
2244
+
2245
+ expect(wrapper.exists()).toBe(true)
2246
+ const result = wrapper.vm.validateOnSubmit()
2247
+ expect(typeof result).toBe('boolean')
2248
+ })
2249
+ })
2250
+
2251
+ describe('Tests des règles de format', () => {
2252
+ it('doit gérer la règle email - DatePicker', async () => {
2253
+ wrapper = mount(DatePicker, {
2254
+ global: {
2255
+ plugins: [vuetify],
2256
+ },
2257
+ props: {
2258
+ modelValue: null,
2259
+ label: 'Champ avec validation email',
2260
+ customRules: [
2261
+ {
2262
+ type: 'email',
2263
+ options: {
2264
+ message: 'Format email invalide',
2265
+ },
2266
+ },
2267
+ ],
2268
+ },
2269
+ })
2270
+
2271
+ await nextTick()
2272
+
2273
+ expect(wrapper.exists()).toBe(true)
2274
+ const result = wrapper.vm.validateOnSubmit()
2275
+ expect(typeof result).toBe('boolean')
2276
+ })
2277
+
2278
+ it('doit gérer la règle matchPattern - CalendarMode', async () => {
2279
+ wrapper = mount(DatePicker, {
2280
+ global: {
2281
+ plugins: [vuetify],
2282
+ },
2283
+ props: {
2284
+ modelValue: null,
2285
+ label: 'Date avec pattern',
2286
+ customRules: [
2287
+ {
2288
+ type: 'matchPattern',
2289
+ options: {
2290
+ pattern: /^\d{2}\/\d{2}\/\d{4}$/,
2291
+ message: 'Format de date invalide',
2292
+ },
2293
+ },
2294
+ ],
2295
+ },
2296
+ })
2297
+
2298
+ await nextTick()
2299
+
2300
+ expect(wrapper.exists()).toBe(true)
2301
+ const result = wrapper.vm.validateOnSubmit()
2302
+ expect(typeof result).toBe('boolean')
2303
+ })
2304
+ })
2305
+
2306
+ describe('Tests des règles de date spécifiques', () => {
2307
+ it('doit gérer la règle notWeekend - DatePicker', async () => {
2308
+ wrapper = mount(DatePicker, {
2309
+ global: {
2310
+ plugins: [vuetify],
2311
+ },
2312
+ props: {
2313
+ useCombinedMode: true,
2314
+ modelValue: '2024-06-15', // Samedi
2315
+ label: 'Date sans weekend',
2316
+ customRules: [
2317
+ {
2318
+ type: 'notWeekend',
2319
+ options: {
2320
+ message: 'Les weekends ne sont pas autorisés',
2321
+ },
2322
+ },
2323
+ ],
2324
+ },
2325
+ })
2326
+
2327
+ await nextTick()
2328
+
2329
+ expect(wrapper.exists()).toBe(true)
2330
+ const result = wrapper.vm.validateOnSubmit()
2331
+ expect(typeof result).toBe('boolean')
2332
+ })
2333
+
2334
+ it('doit gérer la règle notBeforeToday - DatePicker', async () => {
2335
+ wrapper = mount(DatePicker, {
2336
+ global: {
2337
+ plugins: [vuetify],
2338
+ },
2339
+ props: {
2340
+ modelValue: null,
2341
+ label: 'Date pas avant aujourd\'hui',
2342
+ customRules: [
2343
+ {
2344
+ type: 'notBeforeToday',
2345
+ options: {
2346
+ message: 'La date ne peut pas être antérieure à aujourd\'hui',
2347
+ },
2348
+ },
2349
+ ],
2350
+ },
2351
+ })
2352
+
2353
+ await nextTick()
2354
+
2355
+ expect(wrapper.exists()).toBe(true)
2356
+ const result = wrapper.vm.validateOnSubmit()
2357
+ expect(typeof result).toBe('boolean')
2358
+ })
2359
+
2360
+ it('doit gérer la règle notAfterToday - CalendarMode', async () => {
2361
+ wrapper = mount(DatePicker, {
2362
+ global: {
2363
+ plugins: [vuetify],
2364
+ },
2365
+ props: {
2366
+ modelValue: null,
2367
+ label: 'Date pas après aujourd\'hui',
2368
+ customRules: [
2369
+ {
2370
+ type: 'notAfterToday',
2371
+ options: {
2372
+ message: 'La date ne peut pas être postérieure à aujourd\'hui',
2373
+ },
2374
+ },
2375
+ ],
2376
+ },
2377
+ })
2378
+
2379
+ await nextTick()
2380
+
2381
+ expect(wrapper.exists()).toBe(true)
2382
+ const result = wrapper.vm.validateOnSubmit()
2383
+ expect(typeof result).toBe('boolean')
2384
+ })
2385
+
2386
+ it('doit gérer la règle dateExact - DatePicker', async () => {
2387
+ wrapper = mount(DatePicker, {
2388
+ global: {
2389
+ plugins: [vuetify],
2390
+ },
2391
+ props: {
2392
+ useCombinedMode: true,
2393
+ modelValue: null,
2394
+ label: 'Date exacte requise',
2395
+ customRules: [
2396
+ {
2397
+ type: 'dateExact',
2398
+ options: {
2399
+ date: '15/06/2024',
2400
+ message: 'La date doit être exactement le 15/06/2024',
2401
+ },
2402
+ },
2403
+ ],
2404
+ },
2405
+ })
2406
+
2407
+ await nextTick()
2408
+
2409
+ expect(wrapper.exists()).toBe(true)
2410
+ const result = wrapper.vm.validateOnSubmit()
2411
+ expect(typeof result).toBe('boolean')
2412
+ })
2413
+
2414
+ it('doit gérer la règle isHolidayDay - DatePicker', async () => {
2415
+ wrapper = mount(DatePicker, {
2416
+ global: {
2417
+ plugins: [vuetify],
2418
+ },
2419
+ props: {
2420
+ modelValue: null,
2421
+ label: 'Date sans jours fériés',
2422
+ customRules: [
2423
+ {
2424
+ type: 'isHolidayDay',
2425
+ options: {
2426
+ message: 'Les jours fériés ne sont pas autorisés',
2427
+ },
2428
+ },
2429
+ ],
2430
+ },
2431
+ })
2432
+
2433
+ await nextTick()
2434
+
2435
+ expect(wrapper.exists()).toBe(true)
2436
+ const result = wrapper.vm.validateOnSubmit()
2437
+ expect(typeof result).toBe('boolean')
2438
+ })
2439
+ })
2440
+
2441
+ describe('Tests des règles avec valeurs réelles', () => {
2442
+ it('doit valider correctement notBeforeDate avec une vraie date - CalendarMode', async () => {
2443
+ wrapper = mount(DatePicker, {
2444
+ global: {
2445
+ plugins: [vuetify],
2446
+ },
2447
+ props: {
2448
+ modelValue: '10/06/2024', // Date avant la limite
2449
+ label: 'Date avec validation notBeforeDate',
2450
+ customRules: [
2451
+ {
2452
+ type: 'notBeforeDate',
2453
+ options: {
2454
+ date: '15/06/2024',
2455
+ message: 'Date ne peut pas être avant le 15/06/2024',
2456
+ },
2457
+ },
2458
+ ],
2459
+ },
2460
+ })
2461
+
2462
+ await nextTick()
2463
+
2464
+ const result = wrapper.vm.validateOnSubmit()
2465
+ expect(result).toBe(false) // Doit échouer car 10/06 < 15/06
2466
+ })
2467
+
2468
+ it('doit valider correctement notAfterDate avec une vraie date - DatePicker', async () => {
2469
+ wrapper = mount(DatePicker, {
2470
+ global: {
2471
+ plugins: [vuetify],
2472
+ },
2473
+ props: {
2474
+ useCombinedMode: true,
2475
+ modelValue: '20/06/2024', // Date après la limite
2476
+ label: 'Date avec validation notAfterDate',
2477
+ customRules: [
2478
+ {
2479
+ type: 'notAfterDate',
2480
+ options: {
2481
+ date: '15/06/2024',
2482
+ message: 'Date ne peut pas être après le 15/06/2024',
2483
+ },
2484
+ },
2485
+ ],
2486
+ },
2487
+ })
2488
+
2489
+ await nextTick()
2490
+
2491
+ const result = wrapper.vm.validateOnSubmit()
2492
+ expect(result).toBe(false) // Doit échouer car 20/06 > 15/06
2493
+ })
2494
+
2495
+ it('doit valider correctement dateExact avec une vraie date - DatePicker', async () => {
2496
+ wrapper = mount(DatePicker, {
2497
+ global: {
2498
+ plugins: [vuetify],
2499
+ },
2500
+ props: {
2501
+ modelValue: '15/06/2024', // Date exacte
2502
+ label: 'Date avec validation dateExact',
2503
+ customRules: [
2504
+ {
2505
+ type: 'dateExact',
2506
+ options: {
2507
+ date: '15/06/2024',
2508
+ message: 'Date doit être exactement le 15/06/2024',
2509
+ },
2510
+ },
2511
+ ],
2512
+ },
2513
+ })
2514
+
2515
+ await nextTick()
2516
+
2517
+ const result = wrapper.vm.validateOnSubmit()
2518
+ expect(result).toBe(true) // Doit réussir car dates identiques
2519
+ })
2520
+ })
2521
+
2522
+ describe('Tests des règles avec options avancées', () => {
2523
+ it('doit gérer minLength avec ignoreSpace - CalendarMode', async () => {
2524
+ wrapper = mount(DatePicker, {
2525
+ global: {
2526
+ plugins: [vuetify],
2527
+ },
2528
+ props: {
2529
+ modelValue: null,
2530
+ label: 'Date avec minLength ignoreSpace',
2531
+ customRules: [
2532
+ {
2533
+ type: 'minLength',
2534
+ options: {
2535
+ length: 5,
2536
+ ignoreSpace: true,
2537
+ message: 'Minimum 5 caractères sans espaces',
2538
+ },
2539
+ },
2540
+ ],
2541
+ },
2542
+ })
2543
+
2544
+ await nextTick()
2545
+
2546
+ expect(wrapper.exists()).toBe(true)
2547
+ const result = wrapper.vm.validateOnSubmit()
2548
+ expect(typeof result).toBe('boolean')
2549
+ })
2550
+
2551
+ it('doit gérer les règles en mode warning - DatePicker', async () => {
2552
+ wrapper = mount(DatePicker, {
2553
+ global: {
2554
+ plugins: [vuetify],
2555
+ },
2556
+ props: {
2557
+ useCombinedMode: true,
2558
+ modelValue: '2024-06-15',
2559
+ label: 'Date avec warning rules',
2560
+ customWarningRules: [
2561
+ {
2562
+ type: 'notWeekend',
2563
+ options: {
2564
+ isWarning: true,
2565
+ warningMessage: 'Attention: date en weekend',
2566
+ },
2567
+ },
2568
+ ],
2569
+ },
2570
+ })
2571
+
2572
+ await nextTick()
2573
+
2574
+ expect(wrapper.exists()).toBe(true)
2575
+ const result = wrapper.vm.validateOnSubmit()
2576
+ expect(typeof result).toBe('boolean')
2577
+ })
2578
+ })
2579
+ })
2580
+
2581
+ /**
2582
+ * TESTS TOUCHY - CAS LIMITES ET EDGE CASES COMPLEXES
2583
+ */
2584
+ describe('Tests Touchy - Cas Limites et Edge Cases', () => {
2585
+ describe('Tests de concurrence et race conditions', () => {
2586
+ it('doit gérer les changements rapides de modelValue pendant la validation', async () => {
2587
+ wrapper = mount(DatePicker, {
2588
+ global: {
2589
+ plugins: [vuetify],
2590
+ },
2591
+ props: {
2592
+ useCombinedMode: true,
2593
+ modelValue: null,
2594
+ label: 'Date race condition',
2595
+ customRules: [
2596
+ {
2597
+ type: 'notBeforeDate',
2598
+ options: {
2599
+ date: '15/06/2024',
2600
+ message: 'Date trop ancienne',
2601
+ },
2602
+ },
2603
+ ],
2604
+ },
2605
+ })
2606
+
2607
+ await nextTick()
2608
+
2609
+ // Simuler des changements rapides pendant la validation
2610
+ const promises: Promise<void>[] = []
2611
+ for (let i = 0; i < 5; i++) {
2612
+ promises.push(wrapper.setProps({ modelValue: `${10 + i}/06/2024` }))
2613
+ }
2614
+
2615
+ await Promise.all(promises)
2616
+ await nextTick()
2617
+
2618
+ // Le composant doit rester stable
2619
+ expect(wrapper.exists()).toBe(true)
2620
+ const result = wrapper.vm.validateOnSubmit()
2621
+ expect(typeof result).toBe('boolean')
2622
+ })
2623
+
2624
+ it('doit gérer les validations simultanées sur plusieurs instances', async () => {
2625
+ const wrapper1 = mount(DatePicker, {
2626
+ global: {
2627
+ plugins: [vuetify],
2628
+ },
2629
+ props: {
2630
+ modelValue: '10/06/2024',
2631
+ label: 'Date 1',
2632
+ customRules: [
2633
+ {
2634
+ type: 'notBeforeDate',
2635
+ options: {
2636
+ date: '15/06/2024',
2637
+ message: 'Erreur date 1',
2638
+ },
2639
+ },
2640
+ ],
2641
+ },
2642
+ })
2643
+
2644
+ const wrapper2 = mount(DatePicker, {
2645
+ global: {
2646
+ plugins: [vuetify],
2647
+ },
2648
+ props: {
2649
+ useCombinedMode: true,
2650
+ modelValue: '20/06/2024',
2651
+ label: 'Date 2',
2652
+ customRules: [
2653
+ {
2654
+ type: 'notAfterDate',
2655
+ options: {
2656
+ date: '15/06/2024',
2657
+ message: 'Erreur date 2',
2658
+ },
2659
+ },
2660
+ ],
2661
+ },
2662
+ })
2663
+
2664
+ await nextTick()
2665
+
2666
+ // Valider simultanément
2667
+ const [result1, result2] = await Promise.all([
2668
+ Promise.resolve(wrapper1.vm.validateOnSubmit()),
2669
+ Promise.resolve(wrapper2.vm.validateOnSubmit()),
2670
+ ])
2671
+
2672
+ expect(result1).toBe(false) // 10/06 < 15/06
2673
+ expect(result2).toBe(false) // 20/06 > 15/06
2674
+
2675
+ wrapper1.unmount()
2676
+ wrapper2.unmount()
2677
+ })
2678
+ })
2679
+
2680
+ describe('Tests de memory leaks et cleanup', () => {
2681
+ it('doit nettoyer les event listeners lors du unmount - CalendarMode', async () => {
2682
+ const removeEventListenerSpy = vi.spyOn(document, 'removeEventListener')
2683
+
2684
+ wrapper = mount(DatePicker, {
2685
+ global: {
2686
+ plugins: [vuetify],
2687
+ },
2688
+ props: {
2689
+ modelValue: null,
2690
+ label: 'Test cleanup',
2691
+ },
2692
+ })
2693
+
2694
+ await nextTick()
2695
+
2696
+ // Unmount et vérifier le cleanup
2697
+ wrapper.unmount()
2698
+
2699
+ expect(removeEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function))
2700
+ removeEventListenerSpy.mockRestore()
2701
+ })
2702
+
2703
+ it('doit nettoyer les event listeners lors du unmount - DatePicker', async () => {
2704
+ const removeEventListenerSpy = vi.spyOn(document, 'removeEventListener')
2705
+
2706
+ wrapper = mount(DatePicker, {
2707
+ global: {
2708
+ plugins: [vuetify],
2709
+ },
2710
+ props: {
2711
+ useCombinedMode: true,
2712
+ modelValue: null,
2713
+ label: 'Test cleanup Complex',
2714
+ },
2715
+ })
2716
+
2717
+ await nextTick()
2718
+
2719
+ // Unmount et vérifier le cleanup
2720
+ wrapper.unmount()
2721
+
2722
+ expect(removeEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function))
2723
+ removeEventListenerSpy.mockRestore()
2724
+ })
2725
+
2726
+ it('doit gérer les setTimeout sans fuites mémoire', async () => {
2727
+ const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
2728
+
2729
+ wrapper = mount(DatePicker, {
2730
+ global: {
2731
+ plugins: [vuetify],
2732
+ },
2733
+ props: {
2734
+ useCombinedMode: true,
2735
+ modelValue: null,
2736
+ label: 'Test setTimeout',
2737
+ customRules: [
2738
+ {
2739
+ type: 'notBeforeDate',
2740
+ options: {
2741
+ date: '15/06/2024',
2742
+ message: 'Test',
2743
+ },
2744
+ },
2745
+ ],
2746
+ },
2747
+ })
2748
+
2749
+ await nextTick()
2750
+
2751
+ // Changer les règles pour déclencher setTimeout
2752
+ await wrapper.setProps({
2753
+ customRules: [
2754
+ {
2755
+ type: 'notAfterDate',
2756
+ options: {
2757
+ date: '20/06/2024',
2758
+ message: 'Test 2',
2759
+ },
2760
+ },
2761
+ ],
2762
+ })
2763
+
2764
+ await nextTick()
2765
+
2766
+ // Vérifier que setTimeout a été appelé
2767
+ expect(setTimeoutSpy).toHaveBeenCalled()
2768
+
2769
+ // Unmount pour tester le cleanup
2770
+ wrapper.unmount()
2771
+
2772
+ setTimeoutSpy.mockRestore()
2773
+ })
2774
+ })
2775
+
2776
+ describe('Tests de formatage et parsing extrêmes', () => {
2777
+ it('doit gérer les dates avec des années à 2 chiffres', async () => {
2778
+ wrapper = mount(DatePicker, {
2779
+ global: {
2780
+ plugins: [vuetify],
2781
+ },
2782
+ props: {
2783
+ modelValue: null,
2784
+ label: 'Date année 2 chiffres',
2785
+ format: 'DD/MM/YY',
2786
+ },
2787
+ })
2788
+
2789
+ await nextTick()
2790
+
2791
+ const input = wrapper.find('input')
2792
+ await input.setValue('15/06/99')
2793
+ await input.trigger('blur')
2794
+
2795
+ expect(wrapper.exists()).toBe(true)
2796
+ })
2797
+
2798
+ it('doit gérer les dates limites (29 février années bissextiles)', async () => {
2799
+ wrapper = mount(DatePicker, {
2800
+ global: {
2801
+ plugins: [vuetify],
2802
+ },
2803
+ props: {
2804
+ modelValue: '29/02/2024', // 2024 est bissextile
2805
+ label: 'Date bissextile',
2806
+ customRules: [
2807
+ {
2808
+ type: 'dateExact',
2809
+ options: {
2810
+ date: '29/02/2024',
2811
+ message: 'Doit être le 29 février 2024',
2812
+ },
2813
+ },
2814
+ ],
2815
+ },
2816
+ })
2817
+
2818
+ await nextTick()
2819
+
2820
+ const result = wrapper.vm.validateOnSubmit()
2821
+ expect(result).toBe(true) // Doit réussir
2822
+ })
2823
+
2824
+ it('doit rejeter le 29 février pour les années non bissextiles', async () => {
2825
+ wrapper = mount(DatePicker, {
2826
+ global: {
2827
+ plugins: [vuetify],
2828
+ },
2829
+ props: {
2830
+ useCombinedMode: true,
2831
+ modelValue: '29/02/2023', // 2023 n'est pas bissextile
2832
+ label: 'Date non bissextile',
2833
+ },
2834
+ })
2835
+
2836
+ await nextTick()
2837
+
2838
+ const result = wrapper.vm.validateOnSubmit()
2839
+ expect(typeof result).toBe('boolean')
2840
+ })
2841
+
2842
+ it('doit gérer les dates avec des séparateurs mixtes', async () => {
2843
+ wrapper = mount(DatePicker, {
2844
+ global: {
2845
+ plugins: [vuetify],
2846
+ },
2847
+ props: {
2848
+ modelValue: null,
2849
+ label: 'Date séparateurs mixtes',
2850
+ format: 'DD-MM-YYYY',
2851
+ },
2852
+ })
2853
+
2854
+ await nextTick()
2855
+
2856
+ const input = wrapper.find('input')
2857
+ await input.setValue('15-06-2024')
2858
+ await input.trigger('blur')
2859
+
2860
+ expect(wrapper.exists()).toBe(true)
2861
+ })
2862
+ })
2863
+
2864
+ describe('Tests de validation avec règles contradictoires', () => {
2865
+ it('doit gérer des règles contradictoires notBeforeDate et notAfterDate', async () => {
2866
+ wrapper = mount(DatePicker, {
2867
+ global: {
2868
+ plugins: [vuetify],
2869
+ },
2870
+ props: {
2871
+ useCombinedMode: true,
2872
+ modelValue: '15/06/2024',
2873
+ label: 'Règles contradictoires',
2874
+ customRules: [
2875
+ {
2876
+ type: 'notBeforeDate',
2877
+ options: {
2878
+ date: '20/06/2024', // Date doit être >= 20/06
2879
+ message: 'Date trop ancienne',
2880
+ },
2881
+ },
2882
+ {
2883
+ type: 'notAfterDate',
2884
+ options: {
2885
+ date: '10/06/2024', // Date doit être <= 10/06
2886
+ message: 'Date trop récente',
2887
+ },
2888
+ },
2889
+ ],
2890
+ },
2891
+ })
2892
+
2893
+ await nextTick()
2894
+
2895
+ const result = wrapper.vm.validateOnSubmit()
2896
+ expect(result).toBe(false) // Impossible de satisfaire les deux règles
2897
+ })
2898
+
2899
+ it('doit gérer des custom rules qui se contredisent', async () => {
2900
+ const rule1 = vi.fn().mockReturnValue(true)
2901
+ const rule2 = vi.fn().mockReturnValue(false)
2902
+
2903
+ wrapper = mount(DatePicker, {
2904
+ global: {
2905
+ plugins: [vuetify],
2906
+ },
2907
+ props: {
2908
+ modelValue: '15/06/2024',
2909
+ label: 'Custom rules contradictoires',
2910
+ customRules: [
2911
+ {
2912
+ type: 'custom',
2913
+ options: {
2914
+ validate: rule1,
2915
+ message: 'Règle 1 OK',
2916
+ },
2917
+ },
2918
+ {
2919
+ type: 'custom',
2920
+ options: {
2921
+ validate: rule2,
2922
+ message: 'Règle 2 KO',
2923
+ },
2924
+ },
2925
+ ],
2926
+ },
2927
+ })
2928
+
2929
+ await nextTick()
2930
+
2931
+ const result = wrapper.vm.validateOnSubmit()
2932
+ expect(result).toBe(false) // Une règle échoue
2933
+ expect(rule1).toHaveBeenCalled()
2934
+ expect(rule2).toHaveBeenCalled()
2935
+ })
2936
+ })
2937
+
2938
+ describe('Tests de performance avec données massives', () => {
2939
+ it('doit gérer un grand nombre de règles sans ralentissement', async () => {
2940
+ const manyRules = Array.from({ length: 50 }, (_, i) => ({
2941
+ type: 'custom',
2942
+ options: {
2943
+ validate: vi.fn().mockReturnValue(true),
2944
+ message: `Règle ${i + 1}`,
2945
+ },
2946
+ }))
2947
+
2948
+ const startTime = performance.now()
2949
+
2950
+ wrapper = mount(DatePicker, {
2951
+ global: {
2952
+ plugins: [vuetify],
2953
+ },
2954
+ props: {
2955
+ useCombinedMode: true,
2956
+ modelValue: '15/06/2024',
2957
+ label: 'Performance test',
2958
+ customRules: manyRules,
2959
+ },
2960
+ })
2961
+
2962
+ await nextTick()
2963
+
2964
+ const result = wrapper.vm.validateOnSubmit()
2965
+ const endTime = performance.now()
2966
+
2967
+ expect(result).toBe(true)
2968
+ expect(endTime - startTime).toBeLessThan(1000) // Moins d'1 seconde
2969
+
2970
+ // Vérifier que toutes les règles ont été appelées
2971
+ manyRules.forEach((rule) => {
2972
+ expect(rule.options.validate).toHaveBeenCalled()
2973
+ })
2974
+ })
2975
+
2976
+ it('doit gérer les changements rapides de props sans lag', async () => {
2977
+ wrapper = mount(DatePicker, {
2978
+ global: {
2979
+ plugins: [vuetify],
2980
+ },
2981
+ props: {
2982
+ modelValue: null,
2983
+ label: 'Performance props',
2984
+ format: 'DD/MM/YYYY',
2985
+ },
2986
+ })
2987
+
2988
+ await nextTick()
2989
+
2990
+ const startTime = performance.now()
2991
+
2992
+ // Changer rapidement plusieurs props
2993
+ for (let i = 0; i < 20; i++) {
2994
+ await wrapper.setProps({
2995
+ format: i % 2 === 0 ? 'DD/MM/YYYY' : 'MM/DD/YYYY',
2996
+ label: `Label ${i}`,
2997
+ required: i % 2 === 0,
2998
+ })
2999
+ }
3000
+
3001
+ const endTime = performance.now()
3002
+
3003
+ expect(wrapper.exists()).toBe(true)
3004
+ expect(endTime - startTime).toBeLessThan(500) // Moins de 500ms
3005
+ })
3006
+ })
3007
+
3008
+ describe('Tests de réactivité complexe', () => {
3009
+ it('doit gérer les règles réactives avec computed imbriqués', async () => {
3010
+ const offset = ref(5)
3011
+
3012
+ const computedDate = computed(() => {
3013
+ const date = new Date(2024, 5, 15) // 15 juin 2024
3014
+ date.setDate(date.getDate() + offset.value)
3015
+ return `${date.getDate().toString().padStart(2, '0')}/${(date.getMonth() + 1).toString().padStart(2, '0')}/${date.getFullYear()}`
3016
+ })
3017
+
3018
+ const nestedRules = computed(() => [
3019
+ {
3020
+ type: 'notBeforeDate',
3021
+ options: {
3022
+ date: computedDate.value,
3023
+ message: `Date ne peut pas être avant ${computedDate.value}`,
3024
+ },
3025
+ },
3026
+ ])
3027
+
3028
+ wrapper = mount(DatePicker, {
3029
+ global: {
3030
+ plugins: [vuetify],
3031
+ },
3032
+ props: {
3033
+ useCombinedMode: true,
3034
+ modelValue: '18/06/2024',
3035
+ label: 'Règles réactives imbriquées',
3036
+ customRules: nestedRules.value,
3037
+ },
3038
+ })
3039
+
3040
+ await nextTick()
3041
+
3042
+ // Validation initiale
3043
+ let result = wrapper.vm.validateOnSubmit()
3044
+ expect(typeof result).toBe('boolean')
3045
+
3046
+ // Changer l'offset et re-tester
3047
+ offset.value = 10
3048
+ await wrapper.setProps({ customRules: nestedRules.value })
3049
+ await nextTick()
3050
+
3051
+ result = wrapper.vm.validateOnSubmit()
3052
+ expect(typeof result).toBe('boolean')
3053
+ })
3054
+
3055
+ it('doit gérer les règles réactives avec dépendances multiples', async () => {
3056
+ const dateA = ref('15/06/2024')
3057
+ const dateB = ref('20/06/2024')
3058
+
3059
+ // Créer des règles qui dépendent de plusieurs refs
3060
+ const complexRules = computed(() => [
3061
+ {
3062
+ type: 'notBeforeDate',
3063
+ options: {
3064
+ date: dateA.value,
3065
+ message: `Date ne peut pas être avant ${dateA.value}`,
3066
+ },
3067
+ },
3068
+ {
3069
+ type: 'notAfterDate',
3070
+ options: {
3071
+ date: dateB.value,
3072
+ message: `Date ne peut pas être après ${dateB.value}`,
3073
+ },
3074
+ },
3075
+ ])
3076
+
3077
+ wrapper = mount(DatePicker, {
3078
+ global: {
3079
+ plugins: [vuetify],
3080
+ },
3081
+ props: {
3082
+ modelValue: '18/06/2024',
3083
+ label: 'Test règles réactives complexes',
3084
+ customRules: complexRules.value,
3085
+ },
3086
+ })
3087
+
3088
+ await nextTick()
3089
+
3090
+ // Validation initiale
3091
+ let result = wrapper.vm.validateOnSubmit()
3092
+ expect(typeof result).toBe('boolean')
3093
+
3094
+ // Changer les dates de référence
3095
+ dateA.value = '10/06/2024'
3096
+ dateB.value = '25/06/2024'
3097
+ await wrapper.setProps({ customRules: complexRules.value })
3098
+ await nextTick()
3099
+
3100
+ result = wrapper.vm.validateOnSubmit()
3101
+ expect(typeof result).toBe('boolean')
3102
+ })
3103
+ })
3104
+
3105
+ describe('Tests de edge cases spécifiques aux navigateurs', () => {
3106
+ it('doit gérer les événements de collage avec données corrompues', async () => {
3107
+ wrapper = mount(DatePicker, {
3108
+ global: {
3109
+ plugins: [vuetify],
3110
+ },
3111
+ props: {
3112
+ modelValue: null,
3113
+ label: 'Test paste corrompu',
3114
+ format: 'DD/MM/YYYY',
3115
+ },
3116
+ })
3117
+
3118
+ await nextTick()
3119
+
3120
+ const input = wrapper.find('input')
3121
+
3122
+ // Simuler un collage avec données corrompues
3123
+ const pasteEvent = new Event('paste', { bubbles: true })
3124
+ Object.defineProperty(pasteEvent, 'clipboardData', {
3125
+ value: {
3126
+ getData: () => '���invalid���data���',
3127
+ },
3128
+ })
3129
+
3130
+ input.element.dispatchEvent(pasteEvent)
3131
+ await nextTick()
3132
+
3133
+ expect(wrapper.exists()).toBe(true)
3134
+ })
3135
+
3136
+ it('doit gérer les changements de timezone', async () => {
3137
+ wrapper = mount(DatePicker, {
3138
+ global: {
3139
+ plugins: [vuetify],
3140
+ },
3141
+ props: {
3142
+ useCombinedMode: true,
3143
+ modelValue: '15/06/2024',
3144
+ label: 'Test timezone',
3145
+ customRules: [
3146
+ {
3147
+ type: 'notBeforeToday',
3148
+ options: {
3149
+ message: 'Date ne peut pas être avant aujourd\'hui',
3150
+ },
3151
+ },
3152
+ ],
3153
+ },
3154
+ })
3155
+
3156
+ await nextTick()
3157
+
3158
+ const result = wrapper.vm.validateOnSubmit()
3159
+ expect(typeof result).toBe('boolean')
3160
+ })
3161
+ })
3162
+ })
3163
+
3164
+ /**
3165
+ * TESTS COMPLETS DES FORMATS D'ENTRÉE ET DE SORTIE - TOUS LES MODES
3166
+ */
3167
+ describe('Tests Complets des Formats d\'Entrée et de Sortie', () => {
3168
+ describe('Tests CalendarMode - Formats d\'entrée et sortie', () => {
3169
+ const formatCombinations = [
3170
+ { display: 'DD/MM/YYYY', return: 'YYYY-MM-DD', input: '15/06/2024', expected: '2024-06-15' },
3171
+ { display: 'MM/DD/YYYY', return: 'DD/MM/YYYY', input: '06/15/2024', expected: '15/06/2024' },
3172
+ { display: 'DD-MM-YYYY', return: 'MM-DD-YYYY', input: '15-06-2024', expected: '06-15-2024' },
3173
+ { display: 'YYYY/MM/DD', return: 'DD/MM/YYYY', input: '2024/06/15', expected: '15/06/2024' },
3174
+ { display: 'DD.MM.YYYY', return: 'YYYY.MM.DD', input: '15.06.2024', expected: '2024.06.15' },
3175
+ { display: 'DD/MM/YY', return: 'YYYY-MM-DD', input: '15/06/24', expected: '2024-06-15' },
3176
+ { display: 'MM-DD-YY', return: 'DD-MM-YYYY', input: '06-15-24', expected: '15-06-2024' },
3177
+ ]
3178
+
3179
+ formatCombinations.forEach(({ display, return: returnFormat, input }) => {
3180
+ it(`doit gérer format d'affichage ${display} et format de retour ${returnFormat}`, async () => {
3181
+ wrapper = mount(DatePicker, {
3182
+ global: {
3183
+ plugins: [vuetify],
3184
+ },
3185
+ props: {
3186
+ modelValue: null,
3187
+ label: `Test format ${display} → ${returnFormat}`,
3188
+ format: display,
3189
+ dateFormatReturn: returnFormat,
3190
+ },
3191
+ })
3192
+
3193
+ await nextTick()
3194
+
3195
+ // Simuler la sélection d'une date
3196
+ await wrapper.setProps({ modelValue: input })
3197
+ await nextTick()
3198
+
3199
+ expect(wrapper.exists()).toBe(true)
3200
+ expect(wrapper.props('format')).toBe(display)
3201
+ expect(wrapper.props('dateFormatReturn')).toBe(returnFormat)
3202
+ })
3203
+ })
3204
+
3205
+ it('doit gérer les formats avec séparateurs mixtes', async () => {
3206
+ wrapper = mount(DatePicker, {
3207
+ global: {
3208
+ plugins: [vuetify],
3209
+ },
3210
+ props: {
3211
+ modelValue: null,
3212
+ label: 'Format séparateurs mixtes',
3213
+ format: 'DD/MM-YYYY',
3214
+ dateFormatReturn: 'YYYY.MM.DD',
3215
+ },
3216
+ })
3217
+
3218
+ await nextTick()
3219
+
3220
+ expect(wrapper.exists()).toBe(true)
3221
+ expect(wrapper.props('format')).toBe('DD/MM-YYYY')
3222
+ expect(wrapper.props('dateFormatReturn')).toBe('YYYY.MM.DD')
3223
+ })
3224
+
3225
+ it('doit gérer les formats identiques pour affichage et retour', async () => {
3226
+ wrapper = mount(DatePicker, {
3227
+ global: {
3228
+ plugins: [vuetify],
3229
+ },
3230
+ props: {
3231
+ modelValue: '15/06/2024',
3232
+ label: 'Format identique',
3233
+ format: 'DD/MM/YYYY',
3234
+ dateFormatReturn: 'DD/MM/YYYY',
3235
+ },
3236
+ })
3237
+
3238
+ await nextTick()
3239
+
3240
+ expect(wrapper.exists()).toBe(true)
3241
+ const result = wrapper.vm.validateOnSubmit()
3242
+ expect(typeof result).toBe('boolean')
3243
+ })
3244
+
3245
+ it('doit gérer les plages de dates avec formats différents', async () => {
3246
+ wrapper = mount(DatePicker, {
3247
+ global: {
3248
+ plugins: [vuetify],
3249
+ },
3250
+ props: {
3251
+ modelValue: ['15/06/2024', '20/06/2024'],
3252
+ label: 'Plage formats différents',
3253
+ format: 'DD/MM/YYYY',
3254
+ dateFormatReturn: 'YYYY-MM-DD',
3255
+ displayRange: true,
3256
+ },
3257
+ })
3258
+
3259
+ await nextTick()
3260
+
3261
+ expect(wrapper.exists()).toBe(true)
3262
+ expect(wrapper.props('displayRange')).toBe(true)
3263
+ })
3264
+ })
3265
+
3266
+ describe('Tests DatePicker - Formats d\'entrée et sortie', () => {
3267
+ const complexFormatCombinations = [
3268
+ { display: 'DD/MM/YYYY', return: 'ISO', input: '15/06/2024', description: 'Format français vers ISO' },
3269
+ { display: 'MM/DD/YYYY', return: 'DD-MM-YYYY', input: '06/15/2024', description: 'Format US vers européen' },
3270
+ { display: 'YYYY-MM-DD', return: 'DD/MM/YYYY', input: '2024-06-15', description: 'ISO vers français' },
3271
+ { display: 'DD.MM.YY', return: 'YYYY/MM/DD', input: '15.06.24', description: 'Format court vers long' },
3272
+ { display: 'MM-DD-YY', return: 'DD.MM.YYYY', input: '06-15-24', description: 'US court vers européen long' },
3273
+ ]
3274
+
3275
+ complexFormatCombinations.forEach(({ display, return: returnFormat, input, description }) => {
3276
+ it(`doit gérer ${description} (${display} → ${returnFormat})`, async () => {
3277
+ wrapper = mount(DatePicker, {
3278
+ global: {
3279
+ plugins: [vuetify],
3280
+ },
3281
+ props: {
3282
+ useCombinedMode: true,
3283
+ modelValue: null,
3284
+ label: description,
3285
+ format: display,
3286
+ dateFormatReturn: returnFormat,
3287
+ },
3288
+ })
3289
+
3290
+ await nextTick()
3291
+
3292
+ // Tester la saisie dans l'input
3293
+ const input_el = wrapper.find('input')
3294
+ if (input_el.exists()) {
3295
+ await input_el.setValue(input)
3296
+ await input_el.trigger('blur')
3297
+ }
3298
+
3299
+ expect(wrapper.exists()).toBe(true)
3300
+ expect(wrapper.props('format')).toBe(display)
3301
+ expect(wrapper.props('dateFormatReturn')).toBe(returnFormat)
3302
+ })
3303
+ })
3304
+
3305
+ it('doit gérer le mode avec TextFieldActivator et formats différents', async () => {
3306
+ wrapper = mount(DatePicker, {
3307
+ global: {
3308
+ plugins: [vuetify],
3309
+ },
3310
+ props: {
3311
+ useCombinedMode: true,
3312
+ modelValue: null,
3313
+ label: 'Mode TextFieldActivator formats',
3314
+ format: 'DD/MM/YYYY',
3315
+ dateFormatReturn: 'YYYY-MM-DD',
3316
+ textFieldActivator: true,
3317
+ },
3318
+ })
3319
+
3320
+ await nextTick()
3321
+
3322
+ expect(wrapper.exists()).toBe(true)
3323
+ expect(wrapper.props('textFieldActivator')).toBe(true)
3324
+ expect(wrapper.props('format')).toBe('DD/MM/YYYY')
3325
+ expect(wrapper.props('dateFormatReturn')).toBe('YYYY-MM-DD')
3326
+ })
3327
+
3328
+ it('doit gérer les plages avec TextFieldActivator et formats différents', async () => {
3329
+ wrapper = mount(DatePicker, {
3330
+ global: {
3331
+ plugins: [vuetify],
3332
+ },
3333
+ props: {
3334
+ useCombinedMode: true,
3335
+ modelValue: null,
3336
+ label: 'Plage TextFieldActivator',
3337
+ format: 'MM/DD/YYYY',
3338
+ dateFormatReturn: 'DD-MM-YYYY',
3339
+ displayRange: true,
3340
+ textFieldActivator: true,
3341
+ },
3342
+ })
3343
+
3344
+ await nextTick()
3345
+
3346
+ expect(wrapper.exists()).toBe(true)
3347
+ expect(wrapper.props('textFieldActivator')).toBe(true)
3348
+ expect(wrapper.props('displayRange')).toBe(true)
3349
+ })
3350
+
3351
+ it('doit valider avec des règles et formats différents', async () => {
3352
+ wrapper = mount(DatePicker, {
3353
+ global: {
3354
+ plugins: [vuetify],
3355
+ },
3356
+ props: {
3357
+ useCombinedMode: true,
3358
+ modelValue: '15/06/2024',
3359
+ label: 'Validation formats différents',
3360
+ format: 'DD/MM/YYYY',
3361
+ dateFormatReturn: 'YYYY-MM-DD',
3362
+ customRules: [
3363
+ {
3364
+ type: 'notBeforeDate',
3365
+ options: {
3366
+ date: '10/06/2024', // Format d'affichage
3367
+ message: 'Date trop ancienne',
3368
+ },
3369
+ },
3370
+ ],
3371
+ },
3372
+ })
3373
+
3374
+ await nextTick()
3375
+
3376
+ const result = wrapper.vm.validateOnSubmit()
3377
+ expect(result).toBe(true) // 15/06 > 10/06
3378
+ })
3379
+ })
3380
+
3381
+ describe('Tests DatePicker - Formats d\'entrée et sortie', () => {
3382
+ const textInputFormats = [
3383
+ { display: 'DD/MM/YYYY', return: 'YYYY-MM-DD', mask: '##/##/####' },
3384
+ { display: 'MM/DD/YYYY', return: 'DD/MM/YYYY', mask: '##/##/####' },
3385
+ { display: 'DD-MM-YYYY', return: 'MM-DD-YYYY', mask: '##-##-####' },
3386
+ { display: 'YYYY/MM/DD', return: 'DD/MM/YYYY', mask: '####/##/##' },
3387
+ { display: 'DD.MM.YY', return: 'YYYY-MM-DD', mask: '##.##.##' },
3388
+ ]
3389
+
3390
+ textInputFormats.forEach(({ display, return: returnFormat }) => {
3391
+ it(`doit gérer la saisie avec format ${display} et retour ${returnFormat}`, async () => {
3392
+ wrapper = mount(DatePicker, {
3393
+ global: {
3394
+ plugins: [vuetify],
3395
+ },
3396
+ props: {
3397
+ modelValue: null,
3398
+ label: `Saisie ${display}`,
3399
+ format: display,
3400
+ dateFormatReturn: returnFormat,
3401
+ },
3402
+ })
3403
+
3404
+ await nextTick()
3405
+
3406
+ const input = wrapper.find('input')
3407
+ expect(input.exists()).toBe(true)
3408
+ expect(wrapper.props('format')).toBe(display)
3409
+ expect(wrapper.props('dateFormatReturn')).toBe(returnFormat)
3410
+ })
3411
+ })
3412
+
3413
+ it('doit gérer la saisie de plages avec formats différents', async () => {
3414
+ wrapper = mount(DatePicker, {
3415
+ global: {
3416
+ plugins: [vuetify],
3417
+ },
3418
+ props: {
3419
+ modelValue: null,
3420
+ label: 'Plage DatePicker',
3421
+ format: 'DD/MM/YYYY',
3422
+ dateFormatReturn: 'YYYY-MM-DD',
3423
+ displayRange: true,
3424
+ },
3425
+ })
3426
+
3427
+ await nextTick()
3428
+
3429
+ const input = wrapper.find('input')
3430
+ expect(input.exists()).toBe(true)
3431
+ expect(wrapper.props('displayRange')).toBe(true)
3432
+
3433
+ // Tester la saisie d'une plage
3434
+ await input.setValue('15/06/2024 - 20/06/2024')
3435
+ await input.trigger('blur')
3436
+
3437
+ expect(wrapper.exists()).toBe(true)
3438
+ })
3439
+
3440
+ it('doit gérer les formats avec validation en temps réel', async () => {
3441
+ wrapper = mount(DatePicker, {
3442
+ global: {
3443
+ plugins: [vuetify],
3444
+ },
3445
+ props: {
3446
+ modelValue: null,
3447
+ label: 'Validation temps réel',
3448
+ format: 'MM/DD/YYYY',
3449
+ dateFormatReturn: 'DD-MM-YYYY',
3450
+ customRules: [
3451
+ {
3452
+ type: 'notAfterToday',
3453
+ options: {
3454
+ message: 'Date ne peut pas être future',
3455
+ },
3456
+ },
3457
+ ],
3458
+ },
3459
+ })
3460
+
3461
+ await nextTick()
3462
+
3463
+ const input = wrapper.find('input')
3464
+ await input.setValue('12/15/2024')
3465
+ await input.trigger('input')
3466
+ await input.trigger('blur')
3467
+
3468
+ expect(wrapper.exists()).toBe(true)
3469
+ const result = wrapper.vm.validateOnSubmit()
3470
+ expect(typeof result).toBe('boolean')
3471
+ })
3472
+
3473
+ it('doit gérer les formats courts avec années à 2 chiffres', async () => {
3474
+ wrapper = mount(DatePicker, {
3475
+ global: {
3476
+ plugins: [vuetify],
3477
+ },
3478
+ props: {
3479
+ modelValue: null,
3480
+ label: 'Format court YY',
3481
+ format: 'DD/MM/YY',
3482
+ dateFormatReturn: 'YYYY-MM-DD',
3483
+ },
3484
+ })
3485
+
3486
+ await nextTick()
3487
+
3488
+ const input = wrapper.find('input')
3489
+ await input.setValue('15/06/24')
3490
+ await input.trigger('blur')
3491
+
3492
+ expect(wrapper.exists()).toBe(true)
3493
+ expect(wrapper.props('format')).toBe('DD/MM/YY')
3494
+ })
3495
+ })
3496
+
3497
+ describe('Tests de conversion et cohérence entre formats', () => {
3498
+ it('doit maintenir la cohérence lors du changement de format à la volée - CalendarMode', async () => {
3499
+ wrapper = mount(DatePicker, {
3500
+ global: {
3501
+ plugins: [vuetify],
3502
+ },
3503
+ props: {
3504
+ modelValue: '15/06/2024',
3505
+ label: 'Changement format dynamique',
3506
+ format: 'DD/MM/YYYY',
3507
+ dateFormatReturn: 'YYYY-MM-DD',
3508
+ },
3509
+ })
3510
+
3511
+ await nextTick()
3512
+
3513
+ // Changer le format d'affichage
3514
+ await wrapper.setProps({ format: 'MM/DD/YYYY' })
3515
+ await nextTick()
3516
+
3517
+ // Changer le format de retour
3518
+ await wrapper.setProps({ dateFormatReturn: 'DD-MM-YYYY' })
3519
+ await nextTick()
3520
+
3521
+ expect(wrapper.exists()).toBe(true)
3522
+ const result = wrapper.vm.validateOnSubmit()
3523
+ expect(typeof result).toBe('boolean')
3524
+ })
3525
+
3526
+ it('doit gérer les conversions complexes avec validation - DatePicker', async () => {
3527
+ wrapper = mount(DatePicker, {
3528
+ global: {
3529
+ plugins: [vuetify],
3530
+ },
3531
+ props: {
3532
+ useCombinedMode: true,
3533
+ modelValue: '2024-06-15', // Format ISO en entrée
3534
+ label: 'Conversion complexe',
3535
+ format: 'DD/MM/YYYY', // Affichage français
3536
+ dateFormatReturn: 'MM-DD-YYYY', // Retour US
3537
+ customRules: [
3538
+ {
3539
+ type: 'dateExact',
3540
+ options: {
3541
+ date: '15/06/2024', // Date en format d'affichage
3542
+ message: 'Date doit être exactement le 15/06/2024',
3543
+ },
3544
+ },
3545
+ ],
3546
+ },
3547
+ })
3548
+
3549
+ await nextTick()
3550
+
3551
+ const result = wrapper.vm.validateOnSubmit()
3552
+ expect(result).toBe(true) // Doit réussir car dates identiques
3553
+ })
3554
+
3555
+ it('doit gérer les erreurs de format invalide - DatePicker', async () => {
3556
+ wrapper = mount(DatePicker, {
3557
+ global: {
3558
+ plugins: [vuetify],
3559
+ },
3560
+ props: {
3561
+ modelValue: null,
3562
+ label: 'Test format invalide',
3563
+ format: 'DD/MM/YYYY',
3564
+ dateFormatReturn: 'YYYY-MM-DD',
3565
+ },
3566
+ })
3567
+
3568
+ await nextTick()
3569
+
3570
+ const input = wrapper.find('input')
3571
+
3572
+ // Saisir un format invalide
3573
+ await input.setValue('32/13/2024') // Jour et mois invalides
3574
+ await input.trigger('blur')
3575
+
3576
+ expect(wrapper.exists()).toBe(true)
3577
+ const result = wrapper.vm.validateOnSubmit()
3578
+ expect(typeof result).toBe('boolean')
3579
+ })
3580
+
3581
+ it('doit gérer les plages avec formats mixtes - Tous les modes', async () => {
3582
+ const modes = [
3583
+ { component: DatePicker, name: 'CalendarMode' },
3584
+ { component: DatePicker, name: 'DatePicker' },
3585
+ { component: DatePicker, name: 'DatePicker' },
3586
+ ]
3587
+
3588
+ for (const mode of modes) {
3589
+ const testWrapper = mount(mode.component, {
3590
+ global: {
3591
+ plugins: [vuetify],
3592
+ },
3593
+ props: {
3594
+ modelValue: null,
3595
+ label: `Plage formats mixtes ${mode.name}`,
3596
+ format: 'DD/MM/YYYY',
3597
+ dateFormatReturn: 'YYYY-MM-DD',
3598
+ displayRange: true,
3599
+ },
3600
+ })
3601
+
3602
+ await nextTick()
3603
+
3604
+ expect(testWrapper.exists()).toBe(true)
3605
+ expect(testWrapper.props('displayRange')).toBe(true)
3606
+
3607
+ testWrapper.unmount()
3608
+ }
3609
+ })
3610
+ })
3611
+
3612
+ describe('Tests de edge cases avec formats spéciaux', () => {
3613
+ it('doit gérer les formats avec caractères spéciaux', async () => {
3614
+ const specialFormats = [
3615
+ 'DD|MM|YYYY',
3616
+ 'DD\\MM\\YYYY',
3617
+ 'DD MM YYYY',
3618
+ 'DD_MM_YYYY',
3619
+ ]
3620
+
3621
+ for (const format of specialFormats) {
3622
+ wrapper = mount(DatePicker, {
3623
+ global: {
3624
+ plugins: [vuetify],
3625
+ },
3626
+ props: {
3627
+ useCombinedMode: true,
3628
+ modelValue: null,
3629
+ label: `Format spécial ${format}`,
3630
+ format: format,
3631
+ dateFormatReturn: 'YYYY-MM-DD',
3632
+ },
3633
+ })
3634
+
3635
+ await nextTick()
3636
+
3637
+ expect(wrapper.exists()).toBe(true)
3638
+ expect(wrapper.props('format')).toBe(format)
3639
+
3640
+ wrapper.unmount()
3641
+ }
3642
+ })
3643
+
3644
+ it('doit gérer les formats avec ordres non-standards', async () => {
3645
+ const nonStandardFormats = [
3646
+ { format: 'YYYY/DD/MM', description: 'Année/Jour/Mois' },
3647
+ { format: 'MM/YYYY/DD', description: 'Mois/Année/Jour' },
3648
+ { format: 'DD/YYYY/MM', description: 'Jour/Année/Mois' },
3649
+ ]
3650
+
3651
+ for (const { format, description } of nonStandardFormats) {
3652
+ wrapper = mount(DatePicker, {
3653
+ global: {
3654
+ plugins: [vuetify],
3655
+ },
3656
+ props: {
3657
+ modelValue: null,
3658
+ label: description,
3659
+ format: format,
3660
+ dateFormatReturn: 'DD/MM/YYYY',
3661
+ },
3662
+ })
3663
+
3664
+ await nextTick()
3665
+
3666
+ expect(wrapper.exists()).toBe(true)
3667
+ expect(wrapper.props('format')).toBe(format)
3668
+
3669
+ wrapper.unmount()
3670
+ }
3671
+ })
3672
+
3673
+ it('doit gérer les formats avec précision différente', async () => {
3674
+ const precisionFormats = [
3675
+ { format: 'DD/MM/YYYY HH:mm', return: 'YYYY-MM-DD', description: 'Avec heures/minutes' },
3676
+ { format: 'DD/MM/YYYY HH:mm:ss', return: 'YYYY-MM-DD HH:mm:ss', description: 'Avec secondes' },
3677
+ { format: 'DD/MM', return: 'MM/DD', description: 'Sans année' },
3678
+ ]
3679
+
3680
+ for (const { format, return: returnFormat, description } of precisionFormats) {
3681
+ wrapper = mount(DatePicker, {
3682
+ global: {
3683
+ plugins: [vuetify],
3684
+ },
3685
+ props: {
3686
+ modelValue: null,
3687
+ label: description,
3688
+ format: format,
3689
+ dateFormatReturn: returnFormat,
3690
+ },
3691
+ })
3692
+
3693
+ await nextTick()
3694
+
3695
+ expect(wrapper.exists()).toBe(true)
3696
+ expect(wrapper.props('format')).toBe(format)
3697
+ expect(wrapper.props('dateFormatReturn')).toBe(returnFormat)
3698
+
3699
+ wrapper.unmount()
3700
+ }
3701
+ })
3702
+ })
3703
+ })
3704
+
3705
+ /**
3706
+ * TESTS DES ÉVÉNEMENTS (EVENTS) - TOUS LES MODES
3707
+ */
3708
+ describe('Tests des Événements (Events)', () => {
3709
+ describe('Tests des événements CalendarMode', () => {
3710
+ it('doit émettre update:modelValue lors de la sélection d\'une date', async () => {
3711
+ wrapper = mount(DatePicker, {
3712
+ global: {
3713
+ plugins: [vuetify],
3714
+ },
3715
+ props: {
3716
+ modelValue: null,
3717
+ label: 'Test événement update:modelValue',
3718
+ },
3719
+ })
3720
+
3721
+ await nextTick()
3722
+
3723
+ // Vérifier que le composant est monté et peut émettre des événements
3724
+ expect(wrapper.exists()).toBe(true)
3725
+ expect(wrapper.props('modelValue')).toBe(null)
3726
+
3727
+ // Simuler la sélection d'une date via l'API du composant
3728
+ await wrapper.setProps({ modelValue: '15/06/2024' })
3729
+ await nextTick()
3730
+
3731
+ // Le composant doit exister et avoir la bonne valeur
3732
+ expect(wrapper.props('modelValue')).toBe('15/06/2024')
3733
+ })
3734
+
3735
+ it('doit émettre focus lors du focus sur l\'input', async () => {
3736
+ wrapper = mount(DatePicker, {
3737
+ global: {
3738
+ plugins: [vuetify],
3739
+ },
3740
+ props: {
3741
+ modelValue: null,
3742
+ label: 'Test événement focus',
3743
+ },
3744
+ })
3745
+
3746
+ await nextTick()
3747
+
3748
+ // Simuler le focus
3749
+ wrapper.vm.$emit('focus')
3750
+ await nextTick()
3751
+
3752
+ expect(wrapper.emitted('focus')).toBeTruthy()
3753
+ })
3754
+
3755
+ it('doit émettre blur lors de la perte de focus', async () => {
3756
+ wrapper = mount(DatePicker, {
3757
+ global: {
3758
+ plugins: [vuetify],
3759
+ },
3760
+ props: {
3761
+ modelValue: null,
3762
+ label: 'Test événement blur',
3763
+ },
3764
+ })
3765
+
3766
+ await nextTick()
3767
+
3768
+ // Simuler le blur
3769
+ wrapper.vm.$emit('blur')
3770
+ await nextTick()
3771
+
3772
+ expect(wrapper.emitted('blur')).toBeTruthy()
3773
+ })
3774
+
3775
+ it('doit émettre date-selected avec la bonne valeur', async () => {
3776
+ wrapper = mount(DatePicker, {
3777
+ global: {
3778
+ plugins: [vuetify],
3779
+ },
3780
+ props: {
3781
+ modelValue: null,
3782
+ label: 'Test événement date-selected',
3783
+ format: 'DD/MM/YYYY',
3784
+ },
3785
+ })
3786
+
3787
+ await nextTick()
3788
+
3789
+ // Simuler la sélection d'une date
3790
+ wrapper.vm.$emit('date-selected', '15/06/2024')
3791
+ await nextTick()
3792
+
3793
+ expect(wrapper.emitted('date-selected')).toBeTruthy()
3794
+ expect(wrapper.emitted('date-selected')[0]).toEqual(['15/06/2024'])
3795
+ })
3796
+ })
3797
+
3798
+ describe('Tests des événements DatePicker', () => {
3799
+ it('doit émettre update:modelValue avec le bon format', async () => {
3800
+ wrapper = mount(DatePicker, {
3801
+ global: {
3802
+ plugins: [vuetify],
3803
+ },
3804
+ props: {
3805
+ useCombinedMode: true,
3806
+ modelValue: null,
3807
+ label: 'Test événement DatePicker',
3808
+ format: 'DD/MM/YYYY',
3809
+ dateFormatReturn: 'YYYY-MM-DD',
3810
+ },
3811
+ })
3812
+
3813
+ await nextTick()
3814
+
3815
+ // Simuler la sélection
3816
+ await wrapper.setProps({ modelValue: '15/06/2024' })
3817
+ await nextTick()
3818
+
3819
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy()
3820
+ })
3821
+
3822
+ it('doit émettre closed lors de la fermeture du calendrier', async () => {
3823
+ wrapper = mount(DatePicker, {
3824
+ global: {
3825
+ plugins: [vuetify],
3826
+ },
3827
+ props: {
3828
+ useCombinedMode: true,
3829
+ modelValue: null,
3830
+ label: 'Test événement closed',
3831
+ },
3832
+ })
3833
+
3834
+ await nextTick()
3835
+
3836
+ // Simuler la fermeture
3837
+ wrapper.vm.$emit('closed')
3838
+ await nextTick()
3839
+
3840
+ expect(wrapper.emitted('closed')).toBeTruthy()
3841
+ })
3842
+
3843
+ it('doit émettre les événements dans le bon ordre', async () => {
3844
+ wrapper = mount(DatePicker, {
3845
+ global: {
3846
+ plugins: [vuetify],
3847
+ },
3848
+ props: {
3849
+ useCombinedMode: true,
3850
+ modelValue: null,
3851
+ label: 'Test ordre événements',
3852
+ },
3853
+ })
3854
+
3855
+ await nextTick()
3856
+
3857
+ // Simuler une séquence d'événements
3858
+ wrapper.vm.$emit('focus')
3859
+ wrapper.vm.$emit('date-selected', '15/06/2024')
3860
+ wrapper.vm.$emit('update:modelValue', '15/06/2024')
3861
+ wrapper.vm.$emit('blur')
3862
+ await nextTick()
3863
+
3864
+ expect(wrapper.emitted('focus')).toBeTruthy()
3865
+ expect(wrapper.emitted('date-selected')).toBeTruthy()
3866
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy()
3867
+ expect(wrapper.emitted('blur')).toBeTruthy()
3868
+ })
3869
+ })
3870
+
3871
+ describe('Tests des événements DatePicker', () => {
3872
+ it('doit émettre update:model-value lors de la saisie', async () => {
3873
+ wrapper = mount(DatePicker, {
3874
+ global: {
3875
+ plugins: [vuetify],
3876
+ },
3877
+ props: {
3878
+ modelValue: null,
3879
+ label: 'Test événement DatePicker',
3880
+ },
3881
+ })
3882
+
3883
+ await nextTick()
3884
+
3885
+ const input = wrapper.find('input')
3886
+ await input.setValue('15/06/2024')
3887
+ await input.trigger('blur')
3888
+
3889
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy()
3890
+ })
3891
+
3892
+ it('doit émettre input lors de la frappe', async () => {
3893
+ wrapper = mount(DatePicker, {
3894
+ global: {
3895
+ plugins: [vuetify],
3896
+ },
3897
+ props: {
3898
+ modelValue: null,
3899
+ label: 'Test événement input',
3900
+ },
3901
+ })
3902
+
3903
+ await nextTick()
3904
+
3905
+ const input = wrapper.find('input')
3906
+ expect(input.exists()).toBe(true)
3907
+
3908
+ await input.setValue('15')
3909
+ await input.trigger('input')
3910
+
3911
+ // Vérifier que l'input a bien la valeur
3912
+ expect(input.element.value).toContain('15')
3913
+ })
3914
+
3915
+ it('doit émettre focus et blur dans le bon ordre', async () => {
3916
+ wrapper = mount(DatePicker, {
3917
+ global: {
3918
+ plugins: [vuetify],
3919
+ },
3920
+ props: {
3921
+ modelValue: null,
3922
+ label: 'Test focus/blur ordre',
3923
+ },
3924
+ })
3925
+
3926
+ await nextTick()
3927
+
3928
+ const input = wrapper.find('input')
3929
+ await input.trigger('focus')
3930
+ await input.trigger('blur')
3931
+
3932
+ expect(wrapper.emitted('focus')).toBeTruthy()
3933
+ expect(wrapper.emitted('blur')).toBeTruthy()
3934
+ })
3935
+ })
3936
+ })
3937
+
3938
+ /**
3939
+ * TESTS D'ACCESSIBILITÉ (A11Y)
3940
+ */
3941
+ describe('Tests d\'Accessibilité (A11y)', () => {
3942
+ describe('Tests de navigation au clavier', () => {
3943
+ it('doit gérer la navigation au clavier dans DatePicker', async () => {
3944
+ wrapper = mount(DatePicker, {
3945
+ global: {
3946
+ plugins: [vuetify],
3947
+ },
3948
+ props: {
3949
+ modelValue: null,
3950
+ label: 'Test navigation clavier',
3951
+ format: 'DD/MM/YYYY',
3952
+ },
3953
+ })
3954
+
3955
+ await nextTick()
3956
+
3957
+ const input = wrapper.find('input')
3958
+
3959
+ // Test des touches de navigation
3960
+ await input.trigger('keydown', { key: 'ArrowLeft' })
3961
+ await input.trigger('keydown', { key: 'ArrowRight' })
3962
+ await input.trigger('keydown', { key: 'Home' })
3963
+ await input.trigger('keydown', { key: 'End' })
3964
+ await input.trigger('keydown', { key: 'Tab' })
3965
+
3966
+ expect(wrapper.exists()).toBe(true)
3967
+ })
3968
+
3969
+ it('doit gérer la saisie de chiffres au clavier', async () => {
3970
+ wrapper = mount(DatePicker, {
3971
+ global: {
3972
+ plugins: [vuetify],
3973
+ },
3974
+ props: {
3975
+ modelValue: null,
3976
+ label: 'Test saisie chiffres',
3977
+ format: 'DD/MM/YYYY',
3978
+ },
3979
+ })
3980
+
3981
+ await nextTick()
3982
+
3983
+ const input = wrapper.find('input')
3984
+
3985
+ // Test de saisie de chiffres
3986
+ await input.trigger('keydown', { key: '1' })
3987
+ await input.trigger('keydown', { key: '5' })
3988
+ await input.trigger('keydown', { key: '0' })
3989
+ await input.trigger('keydown', { key: '6' })
3990
+
3991
+ expect(wrapper.exists()).toBe(true)
3992
+ })
3993
+
3994
+ it('doit gérer la touche Backspace', async () => {
3995
+ wrapper = mount(DatePicker, {
3996
+ global: {
3997
+ plugins: [vuetify],
3998
+ },
3999
+ props: {
4000
+ modelValue: '15/06/2024',
4001
+ label: 'Test Backspace',
4002
+ format: 'DD/MM/YYYY',
4003
+ },
4004
+ })
4005
+
4006
+ await nextTick()
4007
+
4008
+ const input = wrapper.find('input')
4009
+ await input.trigger('keydown', { key: 'Backspace' })
4010
+
4011
+ expect(wrapper.exists()).toBe(true)
4012
+ })
4013
+ })
4014
+
4015
+ describe('Tests de focus management', () => {
4016
+ it('doit émettre les événements focus et blur - DatePicker', async () => {
4017
+ wrapper = mount(DatePicker, {
4018
+ global: {
4019
+ plugins: [vuetify],
4020
+ },
4021
+ props: {
4022
+ noCalendar: true,
4023
+ modelValue: null,
4024
+ label: 'Test focus methods',
4025
+ },
4026
+ })
4027
+
4028
+ await nextTick()
4029
+
4030
+ const input = wrapper.find('input')
4031
+
4032
+ // Tester l'événement focus
4033
+ await input.trigger('focus')
4034
+ expect(wrapper.emitted('focus')).toBeTruthy()
4035
+
4036
+ // Tester l'événement blur
4037
+ await input.trigger('blur')
4038
+ expect(wrapper.emitted('blur')).toBeTruthy()
4039
+
4040
+ expect(wrapper.exists()).toBe(true)
4041
+ })
4042
+
4043
+ it('doit gérer le focus initial correctement', async () => {
4044
+ wrapper = mount(DatePicker, {
4045
+ global: {
4046
+ plugins: [vuetify],
4047
+ },
4048
+ props: {
4049
+ useCombinedMode: true,
4050
+ modelValue: null,
4051
+ label: 'Test focus initial',
4052
+ autofocus: true,
4053
+ },
4054
+ })
4055
+
4056
+ await nextTick()
4057
+
4058
+ expect(wrapper.exists()).toBe(true)
4059
+ })
4060
+ })
4061
+
4062
+ describe('Tests des attributs ARIA et rôles', () => {
4063
+ it('doit avoir les bons attributs ARIA - CalendarMode', async () => {
4064
+ wrapper = mount(DatePicker, {
4065
+ global: {
4066
+ plugins: [vuetify],
4067
+ },
4068
+ props: {
4069
+ modelValue: null,
4070
+ label: 'Test ARIA',
4071
+ required: true,
4072
+ },
4073
+ })
4074
+
4075
+ await nextTick()
4076
+
4077
+ const input = wrapper.find('input')
4078
+ if (input.exists()) {
4079
+ expect(input.attributes()).toBeDefined()
4080
+ }
4081
+ expect(wrapper.exists()).toBe(true)
4082
+ })
4083
+
4084
+ it('doit supporter les labels et descriptions', async () => {
4085
+ wrapper = mount(DatePicker, {
4086
+ global: {
4087
+ plugins: [vuetify],
4088
+ },
4089
+ props: {
4090
+ modelValue: null,
4091
+ label: 'Date de naissance',
4092
+ required: true,
4093
+ },
4094
+ })
4095
+
4096
+ await nextTick()
4097
+
4098
+ expect(wrapper.props('label')).toBe('Date de naissance')
4099
+ expect(wrapper.props('required')).toBe(true)
4100
+ expect(wrapper.exists()).toBe(true)
4101
+ })
4102
+ })
4103
+ })
4104
+
4105
+ /**
4106
+ * TESTS DES ÉTATS D'ERREUR/WARNING/SUCCESS
4107
+ */
4108
+ describe('Tests des États de Validation', () => {
4109
+ describe('Tests des messages d\'erreur personnalisés', () => {
4110
+ it('doit afficher les messages d\'erreur personnalisés', async () => {
4111
+ wrapper = mount(DatePicker, {
4112
+ global: {
4113
+ plugins: [vuetify],
4114
+ },
4115
+ props: {
4116
+ modelValue: '10/06/2024',
4117
+ label: 'Test erreur personnalisée',
4118
+ customRules: [
4119
+ {
4120
+ type: 'notBeforeDate',
4121
+ options: {
4122
+ date: '15/06/2024',
4123
+ message: 'Message d\'erreur personnalisé très spécifique',
4124
+ },
4125
+ },
4126
+ ],
4127
+ },
4128
+ })
4129
+
4130
+ await nextTick()
4131
+
4132
+ const result = wrapper.vm.validateOnSubmit()
4133
+ expect(result).toBe(false)
4134
+ })
4135
+
4136
+ it('doit gérer les erreurs de validation', async () => {
4137
+ wrapper = mount(DatePicker, {
4138
+ global: {
4139
+ plugins: [vuetify],
4140
+ },
4141
+ props: {
4142
+ modelValue: null,
4143
+ label: 'Test erreurs validation',
4144
+ required: true,
4145
+ customRules: [
4146
+ {
4147
+ type: 'custom',
4148
+ options: {
4149
+ validate: () => false,
4150
+ message: 'Erreur personnalisée',
4151
+ },
4152
+ },
4153
+ ],
4154
+ },
4155
+ })
4156
+
4157
+ await nextTick()
4158
+
4159
+ // Déclencher la validation
4160
+ await wrapper.vm.validateOnSubmit()
4161
+
4162
+ expect(wrapper.vm.errorMessages.length).toBeGreaterThan(0)
4163
+ })
4164
+
4165
+ it('doit combiner erreurs internes et externes', async () => {
4166
+ wrapper = mount(DatePicker, {
4167
+ global: {
4168
+ plugins: [vuetify],
4169
+ },
4170
+ props: {
4171
+ useCombinedMode: true,
4172
+ modelValue: null,
4173
+ label: 'Test erreurs combinées',
4174
+ required: true,
4175
+ errorMessages: ['Erreur externe'],
4176
+ customRules: [
4177
+ {
4178
+ type: 'custom',
4179
+ options: {
4180
+ validate: () => false,
4181
+ message: 'Erreur interne',
4182
+ },
4183
+ },
4184
+ ],
4185
+ },
4186
+ })
4187
+
4188
+ await nextTick()
4189
+
4190
+ const result = wrapper.vm.validateOnSubmit()
4191
+ expect(result).toBe(false)
4192
+ })
4193
+ })
4194
+
4195
+ describe('Tests des warnings', () => {
4196
+ it('doit afficher les warnings sans bloquer la validation', async () => {
4197
+ wrapper = mount(DatePicker, {
4198
+ global: {
4199
+ plugins: [vuetify],
4200
+ },
4201
+ props: {
4202
+ useCombinedMode: true,
4203
+ modelValue: '15/06/2024',
4204
+ label: 'Test warnings',
4205
+ customWarningRules: [
4206
+ {
4207
+ type: 'custom',
4208
+ options: {
4209
+ validate: () => false,
4210
+ message: 'Ceci est un warning',
4211
+ },
4212
+ },
4213
+ ],
4214
+ },
4215
+ })
4216
+
4217
+ await nextTick()
4218
+
4219
+ const result = wrapper.vm.validateOnSubmit()
4220
+ expect(typeof result).toBe('boolean')
4221
+ })
4222
+
4223
+ it('doit gérer les warnings et erreurs simultanément', async () => {
4224
+ wrapper = mount(DatePicker, {
4225
+ global: {
4226
+ plugins: [vuetify],
4227
+ },
4228
+ props: {
4229
+ modelValue: '15/06/2024',
4230
+ label: 'Test warnings + erreurs',
4231
+ customRules: [
4232
+ {
4233
+ type: 'custom',
4234
+ options: {
4235
+ validate: () => false,
4236
+ message: 'Erreur critique',
4237
+ },
4238
+ },
4239
+ ],
4240
+ customWarningRules: [
4241
+ {
4242
+ type: 'custom',
4243
+ options: {
4244
+ validate: () => false,
4245
+ message: 'Warning informatif',
4246
+ },
4247
+ },
4248
+ ],
4249
+ },
4250
+ })
4251
+
4252
+ await nextTick()
4253
+
4254
+ const result = wrapper.vm.validateOnSubmit()
4255
+ expect(result).toBe(false) // Erreur doit bloquer
4256
+ })
4257
+ })
4258
+
4259
+ describe('Tests des messages de succès', () => {
4260
+ it('doit afficher les messages de succès', async () => {
4261
+ wrapper = mount(DatePicker, {
4262
+ global: {
4263
+ plugins: [vuetify],
4264
+ },
4265
+ props: {
4266
+ modelValue: '15/06/2024',
4267
+ label: 'Test succès',
4268
+ customRules: [
4269
+ {
4270
+ type: 'custom',
4271
+ options: {
4272
+ validate: () => true,
4273
+ message: 'Date valide !',
4274
+ },
4275
+ },
4276
+ ],
4277
+ },
4278
+ })
4279
+
4280
+ await nextTick()
4281
+
4282
+ const result = wrapper.vm.validateOnSubmit()
4283
+ expect(result).toBe(true)
4284
+ })
4285
+ })
4286
+ })
4287
+
4288
+ /**
4289
+ * TESTS DE PERFORMANCE ET OPTIMISATION
4290
+ */
4291
+ describe('Tests de Performance et Optimisation', () => {
4292
+ describe('Tests de debouncing et throttling', () => {
4293
+ it('doit gérer la validation en temps réel sans surcharge', async () => {
4294
+ const mockValidate = vi.fn().mockReturnValue(true)
4295
+
4296
+ wrapper = mount(DatePicker, {
4297
+ global: {
4298
+ plugins: [vuetify],
4299
+ },
4300
+ props: {
4301
+ modelValue: null,
4302
+ label: 'Test performance validation',
4303
+ customRules: [
4304
+ {
4305
+ type: 'custom',
4306
+ options: {
4307
+ validate: mockValidate,
4308
+ message: 'Test performance',
4309
+ },
4310
+ },
4311
+ ],
4312
+ },
4313
+ })
4314
+
4315
+ await nextTick()
4316
+
4317
+ const input = wrapper.find('input')
4318
+
4319
+ // Saisie rapide
4320
+ for (let i = 0; i < 10; i++) {
4321
+ await input.setValue(`${i}`)
4322
+ await input.trigger('input')
4323
+ }
4324
+
4325
+ await nextTick()
4326
+
4327
+ expect(wrapper.exists()).toBe(true)
4328
+ // La validation ne doit pas être appelée excessivement
4329
+ })
4330
+
4331
+ it('doit optimiser les re-renders lors de changements rapides', async () => {
4332
+ wrapper = mount(DatePicker, {
4333
+ global: {
4334
+ plugins: [vuetify],
4335
+ },
4336
+ props: {
4337
+ useCombinedMode: true,
4338
+ modelValue: null,
4339
+ label: 'Test re-renders',
4340
+ },
4341
+ })
4342
+
4343
+ await nextTick()
4344
+
4345
+ const startTime = performance.now()
4346
+
4347
+ // Changements rapides de props
4348
+ for (let i = 0; i < 20; i++) {
4349
+ await wrapper.setProps({
4350
+ modelValue: `${10 + i}/06/2024`,
4351
+ label: `Label ${i}`,
4352
+ })
4353
+ }
4354
+
4355
+ const endTime = performance.now()
4356
+
4357
+ expect(wrapper.exists()).toBe(true)
4358
+ expect(endTime - startTime).toBeLessThan(1000) // Moins d'1 seconde
4359
+ })
4360
+ })
4361
+
4362
+ describe('Tests de memory leaks avancés', () => {
4363
+ it('doit nettoyer les watchers lors du unmount', async () => {
4364
+ wrapper = mount(DatePicker, {
4365
+ global: {
4366
+ plugins: [vuetify],
4367
+ },
4368
+ props: {
4369
+ useCombinedMode: true,
4370
+ modelValue: null,
4371
+ label: 'Test watchers cleanup',
4372
+ customRules: [
4373
+ {
4374
+ type: 'notBeforeDate',
4375
+ options: {
4376
+ date: '15/06/2024',
4377
+ message: 'Test',
4378
+ },
4379
+ },
4380
+ ],
4381
+ },
4382
+ })
4383
+
4384
+ await nextTick()
4385
+
4386
+ // Unmount et vérifier le cleanup
4387
+ wrapper.unmount()
4388
+
4389
+ expect(wrapper.exists()).toBe(false)
4390
+ })
4391
+
4392
+ it('doit gérer les instances multiples sans interférence', async () => {
4393
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- tests
4394
+ const wrappers: any[] = []
4395
+
4396
+ // Créer plusieurs instances
4397
+ for (let i = 0; i < 5; i++) {
4398
+ const w = mount(DatePicker, {
4399
+ global: {
4400
+ plugins: [vuetify],
4401
+ },
4402
+ props: {
4403
+ modelValue: null,
4404
+ label: `Instance ${i}`,
4405
+ format: 'DD/MM/YYYY',
4406
+ },
4407
+ })
4408
+ wrappers.push(w)
4409
+ }
4410
+
4411
+ await nextTick()
4412
+
4413
+ // Vérifier que toutes les instances fonctionnent
4414
+ for (const w of wrappers) {
4415
+ expect(w.exists()).toBe(true)
4416
+ w.unmount()
4417
+ }
4418
+ })
4419
+ })
4420
+ })
4421
+
4422
+ /**
4423
+ * TESTS D'INTÉGRATION AVEC FORMULAIRES
4424
+ */
4425
+ describe('Tests d\'Intégration avec Formulaires', () => {
4426
+ describe('Tests avec Vuetify forms', () => {
4427
+ it('doit s\'intégrer correctement dans un v-form', async () => {
4428
+ const formWrapper = mount({
4429
+ template: `
4430
+ <v-form ref="form">
4431
+ <DatePicker
4432
+ v-model="date"
4433
+ label="Date dans formulaire"
4434
+ :rules="[required]"
4435
+ />
4436
+ </v-form>
4437
+ `,
4438
+ components: {
4439
+ DatePicker,
4440
+ },
4441
+ setup() {
4442
+ const date = ref(null)
4443
+ const required = value => !!value || 'Date requise'
4444
+ return { date, required }
4445
+ },
4446
+ }, {
4447
+ global: {
4448
+ plugins: [vuetify],
4449
+ },
4450
+ })
4451
+
4452
+ await nextTick()
4453
+
4454
+ expect(formWrapper.exists()).toBe(true)
4455
+ formWrapper.unmount()
4456
+ })
4457
+
4458
+ it('doit participer à la validation globale du formulaire', async () => {
4459
+ const formWrapper = mount({
4460
+ template: `
4461
+ <v-form ref="form" v-model="valid">
4462
+ <DatePicker
4463
+ v-model="date"
4464
+ label="Date validation globale"
4465
+ required
4466
+ />
4467
+ </v-form>
4468
+ `,
4469
+ components: {
4470
+ DatePicker,
4471
+ },
4472
+ setup() {
4473
+ const date = ref(null)
4474
+ const valid = ref(false)
4475
+ return { date, valid }
4476
+ },
4477
+ }, {
4478
+ global: {
4479
+ plugins: [vuetify],
4480
+ },
4481
+ })
4482
+
4483
+ await nextTick()
4484
+
4485
+ expect(formWrapper.exists()).toBe(true)
4486
+ formWrapper.unmount()
4487
+ })
4488
+ })
4489
+
4490
+ describe('Tests de reset de formulaire', () => {
4491
+ it('doit se réinitialiser lors du reset du formulaire', async () => {
4492
+ wrapper = mount(DatePicker, {
4493
+ global: {
4494
+ plugins: [vuetify],
4495
+ },
4496
+ props: {
4497
+ modelValue: '15/06/2024',
4498
+ label: 'Test reset',
4499
+ },
4500
+ })
4501
+
4502
+ await nextTick()
4503
+
4504
+ // Simuler un reset
4505
+ await wrapper.setProps({ modelValue: null })
4506
+ await nextTick()
4507
+
4508
+ expect(wrapper.props('modelValue')).toBe(null)
4509
+ })
4510
+
4511
+ it('doit nettoyer les erreurs lors du reset', async () => {
4512
+ wrapper = mount(DatePicker, {
4513
+ global: {
4514
+ plugins: [vuetify],
4515
+ },
4516
+ props: {
4517
+ useCombinedMode: true,
4518
+ modelValue: null,
4519
+ label: 'Test reset erreurs',
4520
+ required: true,
4521
+ },
4522
+ })
4523
+
4524
+ await nextTick()
4525
+
4526
+ // Déclencher une erreur
4527
+ const result = wrapper.vm.validateOnSubmit()
4528
+ expect(result).toBe(false)
4529
+
4530
+ // Reset
4531
+ await wrapper.setProps({ modelValue: '15/06/2024' })
4532
+ await nextTick()
4533
+
4534
+ const newResult = wrapper.vm.validateOnSubmit()
4535
+ expect(newResult).toBe(true)
4536
+ })
4537
+ })
4538
+
4539
+ describe('Tests de soumission de formulaire', () => {
4540
+ it('doit valider avant soumission', async () => {
4541
+ wrapper = mount(DatePicker, {
4542
+ global: {
4543
+ plugins: [vuetify],
4544
+ },
4545
+ props: {
4546
+ modelValue: null,
4547
+ label: 'Test soumission',
4548
+ required: true,
4549
+ customRules: [
4550
+ {
4551
+ type: 'notAfterToday',
4552
+ options: {
4553
+ message: 'Date future interdite',
4554
+ },
4555
+ },
4556
+ ],
4557
+ },
4558
+ })
4559
+
4560
+ await nextTick()
4561
+
4562
+ // Test avec date invalide
4563
+ await wrapper.setProps({ modelValue: '15/12/2025' })
4564
+ await nextTick()
4565
+
4566
+ const result = wrapper.vm.validateOnSubmit()
4567
+ expect(typeof result).toBe('boolean')
4568
+ })
4569
+
4570
+ it('doit retourner les bonnes valeurs pour la soumission', async () => {
4571
+ wrapper = mount(DatePicker, {
4572
+ global: {
4573
+ plugins: [vuetify],
4574
+ },
4575
+ props: {
4576
+ modelValue: '15/06/2024',
4577
+ label: 'Test valeurs soumission',
4578
+ format: 'DD/MM/YYYY',
4579
+ dateFormatReturn: 'YYYY-MM-DD',
4580
+ },
4581
+ })
4582
+
4583
+ await nextTick()
4584
+
4585
+ expect(wrapper.props('modelValue')).toBe('15/06/2024')
4586
+ expect(wrapper.props('dateFormatReturn')).toBe('YYYY-MM-DD')
4587
+ })
4588
+ })
4589
+ })
4590
+
4591
+ /**
4592
+ * TESTS TRANSVERSAUX POUR TOUS LES MODES
4593
+ */
4594
+ describe('Tests Transversaux - Tous les Modes', () => {
4595
+ const modes = [
4596
+ { name: 'CalendarMode', component: DatePicker, props: {} },
4597
+ { name: 'DatePicker', component: DatePicker, props: {} },
4598
+ { name: 'DatePicker Combined', component: DatePicker, props: { useCombinedMode: true } },
4599
+ { name: 'DatePicker', component: DatePicker, props: {} },
4600
+ ]
4601
+
4602
+ modes.forEach(({ name, component, props }) => {
4603
+ describe(`${name}`, () => {
4604
+ it('doit gérer les custom rules avec valeurs null', async () => {
4605
+ const customRuleMock = vi.fn().mockReturnValue(true)
4606
+ const customRules = [
4607
+ {
4608
+ type: 'custom',
4609
+ options: {
4610
+ validate: customRuleMock,
4611
+ message: 'Test null',
4612
+ },
4613
+ },
4614
+ ]
4615
+
4616
+ wrapper = mount(component, {
4617
+ global: {
4618
+ plugins: [vuetify],
4619
+ },
4620
+ props: {
4621
+ modelValue: null,
4622
+ label: `Date ${name}`,
4623
+ customRules,
4624
+ ...props,
4625
+ },
4626
+ })
4627
+
4628
+ await nextTick()
4629
+
4630
+ wrapper.vm.validateOnSubmit()
4631
+ expect(customRuleMock).toHaveBeenCalledWith(null)
4632
+ })
4633
+
4634
+ it('doit retourner true quand aucune règle n\'est définie', async () => {
4635
+ wrapper = mount(component, {
4636
+ global: {
4637
+ plugins: [vuetify],
4638
+ },
4639
+ props: {
4640
+ modelValue: null,
4641
+ label: `Date ${name}`,
4642
+ ...props,
4643
+ },
4644
+ })
4645
+
4646
+ await nextTick()
4647
+
4648
+ const result = wrapper.vm.validateOnSubmit()
4649
+ expect(result).toBe(true)
4650
+ })
4651
+ })
4652
+ })
4653
+ })
4654
+ })