@cnamts/synapse 1.1.0 → 1.1.1

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 (202) hide show
  1. package/dist/{AutocompleteFilter-DXd4szWO.js → AutocompleteFilter-CGF33skz.js} +1 -1
  2. package/dist/{DateFilter-BD59Kgwf.js → DateFilter-D7-MsKtx.js} +1 -1
  3. package/dist/{NumberFilter-BSMZE7uw.js → NumberFilter-bjQPPfsj.js} +1 -1
  4. package/dist/{PeriodFilter-keUdSSk0.js → PeriodFilter-B3wJpK8-.js} +1 -1
  5. package/dist/{SelectFilter-Dhvvwazl.js → SelectFilter-BN6DbKAV.js} +1 -1
  6. package/dist/{TextFilter-CU8FpXz0.js → TextFilter-BffP0J2f.js} +1 -1
  7. package/dist/{apLightTheme2026-DbS7BPUf.js → apLightTheme2026-C4ygwMHC.js} +11 -11
  8. package/dist/components/Amelipro/AmeliproAutoCompleteField/AmeliproAutoCompleteField.d.ts +6 -6
  9. package/dist/components/Amelipro/AmeliproSelect/AmeliproSelect.d.ts +6 -6
  10. package/dist/components/Amelipro/AmeliproTabs/AmeliproTabs.d.ts +6 -6
  11. package/dist/components/Captcha/Captcha.d.ts +27 -16
  12. package/dist/components/Captcha/CaptchaForm.d.ts +29 -3
  13. package/dist/components/Captcha/types.d.ts +14 -0
  14. package/dist/components/Captcha/useCaptchaValidation.d.ts +37 -0
  15. package/dist/components/Customs/Selects/SelectBtnField/SelectBtnField.d.ts +33 -13
  16. package/dist/components/Customs/Selects/SelectBtnField/composables/useSelectBtnFieldValidation.d.ts +23 -0
  17. package/dist/components/Customs/Selects/SyAutocomplete/composables/useSyAutocompleteValidation.d.ts +2 -2
  18. package/dist/components/Customs/Selects/SySelect/composables/useSySelectValidation.d.ts +2 -2
  19. package/dist/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.d.ts +17 -48
  20. package/dist/components/Customs/SyCheckBoxGroup/composables/useSyCheckBoxGroupValidation.d.ts +29 -0
  21. package/dist/components/Customs/SyCheckBoxGroup/types.d.ts +46 -0
  22. package/dist/components/Customs/SyCheckbox/SyCheckbox.d.ts +16 -51
  23. package/dist/components/Customs/SyCheckbox/composables/useSyCheckboxValidation.d.ts +27 -0
  24. package/dist/components/Customs/SyCheckbox/types.d.ts +49 -0
  25. package/dist/components/Customs/SyTextField/FieldState.d.ts +5 -0
  26. package/dist/components/Customs/SyTextField/useSyTextFieldValidation.d.ts +3 -3
  27. package/dist/components/DialogBox/DialogBox.d.ts +2 -0
  28. package/dist/components/DialogBox/locales.d.ts +1 -0
  29. package/dist/components/FilterSideBar/FilterSideBar.d.ts +4 -0
  30. package/dist/components/LunarCalendar/LunarCalendar.d.ts +43 -14
  31. package/dist/components/LunarCalendar/types.d.ts +35 -0
  32. package/dist/components/LunarCalendar/useLunarCalendarValidation.d.ts +11 -12
  33. package/dist/components/MonthPicker/MonthPicker.d.ts +72 -1747
  34. package/dist/components/MonthPicker/MonthPickerText/MonthPickerInput.d.ts +21 -1733
  35. package/dist/components/MonthPicker/MonthPickerText/useTextField.d.ts +5 -0
  36. package/dist/components/MonthPicker/locales.d.ts +1 -0
  37. package/dist/components/MonthPicker/types.d.ts +11 -0
  38. package/dist/components/MonthPicker/useMonthPickerValidation.d.ts +37 -24
  39. package/dist/components/NirField/NirField.d.ts +6 -4
  40. package/dist/components/NirField/useNirValidation.d.ts +7 -5
  41. package/dist/components/PageContainer/PageContainer.d.ts +8 -0
  42. package/dist/components/PasswordField/PasswordField.d.ts +2 -2
  43. package/dist/components/PasswordField/usePasswordFieldValidation.d.ts +2 -2
  44. package/dist/components/PhoneField/PhoneField.d.ts +960 -1938
  45. package/dist/components/PhoneField/indicatifs.d.ts +715 -8
  46. package/dist/components/PhoneField/locales.d.ts +7 -0
  47. package/dist/components/PhoneField/types.d.ts +29 -0
  48. package/dist/components/PhoneField/usePhoneFieldValidation.d.ts +45 -0
  49. package/dist/components/PhoneField/usePhoneIndicatifs.d.ts +947 -0
  50. package/dist/components/SyTextArea/composables/useSyTextAreaValidation.d.ts +2 -2
  51. package/dist/composables/unifyValidation/documentationValidationProps.d.ts +1 -1
  52. package/dist/composables/unifyValidation/useValidation.d.ts +4 -5
  53. package/dist/design-system-v3.js +2 -2
  54. package/dist/designTokens/tokens/amelipro/apLightTheme.d.ts +10 -10
  55. package/dist/designTokens/tokens/baseTokens.d.ts +18 -18
  56. package/dist/designTokens/tokens/cnam/cnamLightTheme.d.ts +10 -10
  57. package/dist/designTokens/tokens/pa/paLightTheme.d.ts +10 -10
  58. package/dist/designTokens/tokens/semanticTokens.d.ts +14 -14
  59. package/dist/{main-D8ryUoS5.js → main-C4wAktOs.js} +13718 -12991
  60. package/dist/synapse.css +1 -1
  61. package/dist/vuetifyConfig.js +1 -1
  62. package/package.json +7 -7
  63. package/src/assets/compat/_legacy-tokens.scss +91 -0
  64. package/src/assets/overrides/_utilities.scss +23 -0
  65. package/src/components/Accordion/Accordion.stories.ts +121 -1
  66. package/src/components/BackBtn/BackBtn.mdx +1 -1
  67. package/src/components/BackToTopBtn/BackToTopBtn.mdx +0 -1
  68. package/src/components/Captcha/Captcha.stories.ts +134 -31
  69. package/src/components/Captcha/Captcha.vue +95 -28
  70. package/src/components/Captcha/CaptchaForm.vue +51 -22
  71. package/src/components/Captcha/tests/Captcha.focus.spec.ts +214 -0
  72. package/src/components/Captcha/tests/Captcha.spec.ts +233 -24
  73. package/src/components/Captcha/tests/CaptchaForm.spec.ts +82 -0
  74. package/src/components/Captcha/tests/__snapshots__/Captcha.spec.ts.snap +16 -42
  75. package/src/components/Captcha/types.ts +15 -0
  76. package/src/components/Captcha/useCaptchaValidation.ts +87 -0
  77. package/src/components/Captcha/validation/validation.stories.ts +1194 -0
  78. package/src/components/ChipList/ChipList.mdx +0 -1
  79. package/src/components/CollapsibleList/CollapsibleList.mdx +0 -1
  80. package/src/components/CookieBanner/CookieBanner.mdx +0 -1
  81. package/src/components/CopyBtn/CopyBtn.mdx +0 -1
  82. package/src/components/Customs/Selects/SelectBtnField/SelectBtnField.stories.ts +123 -439
  83. package/src/components/Customs/Selects/SelectBtnField/SelectBtnField.vue +147 -41
  84. package/src/components/Customs/Selects/SelectBtnField/Validation/Validation.stories.ts +600 -0
  85. package/src/components/Customs/Selects/SelectBtnField/composables/useSelectBtnFieldValidation.ts +87 -0
  86. package/src/components/Customs/Selects/SelectBtnField/tests/SelectBtnField.spec.ts +402 -33
  87. package/src/components/Customs/Selects/SelectBtnField/tests/__snapshots__/SelectBtnField.spec.ts.snap +52 -38
  88. package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.stories.ts +342 -162
  89. package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.vue +77 -129
  90. package/src/components/Customs/SyCheckBoxGroup/Validation/Validation.stories.ts +1008 -0
  91. package/src/components/Customs/SyCheckBoxGroup/composables/useSyCheckBoxGroupValidation.ts +107 -0
  92. package/src/components/Customs/SyCheckBoxGroup/tests/SyCheckBoxGroup.spec.ts +180 -7
  93. package/src/components/Customs/SyCheckBoxGroup/types.ts +49 -0
  94. package/src/components/Customs/SyCheckbox/SyCheckbox.stories.ts +41 -161
  95. package/src/components/Customs/SyCheckbox/SyCheckbox.vue +71 -148
  96. package/src/components/Customs/SyCheckbox/Validation/Validation.stories.ts +654 -0
  97. package/src/components/Customs/SyCheckbox/composables/useSyCheckboxValidation.ts +105 -0
  98. package/src/components/Customs/SyCheckbox/tests/SyCheckbox.spec.ts +106 -0
  99. package/src/components/Customs/SyCheckbox/tests/useSyCheckboxValidation.spec.ts +98 -0
  100. package/src/components/Customs/SyCheckbox/types.ts +51 -0
  101. package/src/components/Customs/SyTextField/FieldState.vue +50 -0
  102. package/src/components/Customs/SyTextField/SyTextField.vue +12 -9
  103. package/src/components/Customs/SyTextField/useSyTextFieldValidation.ts +2 -11
  104. package/src/components/DataList/DataList.mdx +0 -1
  105. package/src/components/DataListGroup/DataListGroup.mdx +0 -1
  106. package/src/components/DiacriticPicker/DiacriticPicker.mdx +0 -1
  107. package/src/components/DialogBox/DialogBox.mdx +0 -1
  108. package/src/components/DialogBox/DialogBox.stories.ts +399 -4
  109. package/src/components/DialogBox/DialogBox.vue +20 -0
  110. package/src/components/DialogBox/locales.ts +1 -0
  111. package/src/components/DialogBox/tests/DialogBox.spec.ts +73 -0
  112. package/src/components/DialogBox/tests/DialogBox.visual.cy.ts +24 -0
  113. package/src/components/ErrorPage/ErrorPage.mdx +1 -1
  114. package/src/components/ExternalLinks/ExternalLinks.mdx +0 -1
  115. package/src/components/FileList/FileList.mdx +0 -1
  116. package/src/components/FilterInline/FilterInline.mdx +0 -1
  117. package/src/components/FilterSideBar/FilterSideBar.mdx +8 -1
  118. package/src/components/FilterSideBar/FilterSideBar.stories.ts +133 -1
  119. package/src/components/FilterSideBar/FilterSideBar.vue +19 -2
  120. package/src/components/FilterSideBar/tests/FilterSideBar.spec.ts +55 -0
  121. package/src/components/FooterBar/FooterBar.mdx +0 -1
  122. package/src/components/FranceConnectBtn/FranceConnectBtn.mdx +0 -1
  123. package/src/components/HeaderBar/HeaderBar.mdx +0 -1
  124. package/src/components/HeaderLoading/HeaderLoading.mdx +0 -1
  125. package/src/components/LangBtn/LangBtn.mdx +0 -1
  126. package/src/components/Logo/Logo.mdx +1 -1
  127. package/src/components/LunarCalendar/LunarCalendar.mdx +6 -9
  128. package/src/components/LunarCalendar/LunarCalendar.stories.ts +243 -46
  129. package/src/components/LunarCalendar/LunarCalendar.vue +61 -26
  130. package/src/components/LunarCalendar/Validation/Validation.stories.ts +717 -0
  131. package/src/components/LunarCalendar/tests/LunarCalendar.a11y.spec.ts +1 -1
  132. package/src/components/LunarCalendar/tests/LunarCalendar.spec.ts +197 -6
  133. package/src/components/LunarCalendar/tests/useLunarCalendarValidation.spec.ts +287 -0
  134. package/src/components/LunarCalendar/types.ts +39 -0
  135. package/src/components/LunarCalendar/useLunarCalendarValidation.ts +115 -39
  136. package/src/components/MonthPicker/MonthPicker.stories.ts +38 -281
  137. package/src/components/MonthPicker/MonthPicker.vue +66 -17
  138. package/src/components/MonthPicker/MonthPickerText/MonthPickerInput.vue +44 -20
  139. package/src/components/MonthPicker/MonthPickerText/useTextField.ts +5 -0
  140. package/src/components/MonthPicker/Validation/Validation.stories.ts +1117 -0
  141. package/src/components/MonthPicker/locales.ts +1 -0
  142. package/src/components/MonthPicker/tests/MonthPicker.spec.ts +353 -2
  143. package/src/components/MonthPicker/tests/__snapshots__/MonthPicker.spec.ts.snap +12 -8
  144. package/src/components/MonthPicker/types.ts +16 -0
  145. package/src/components/MonthPicker/useMonthPickerValidation.ts +64 -27
  146. package/src/components/NirField/NirField.mdx +120 -66
  147. package/src/components/NirField/NirField.stories.ts +216 -0
  148. package/src/components/NirField/useNirValidation.ts +16 -17
  149. package/src/components/NotFoundPage/tests/__snapshots__/NotFoundPage.spec.ts.snap +263 -245
  150. package/src/components/NotificationBar/NotificationBar.mdx +0 -1
  151. package/src/components/PageContainer/PageContainer.mdx +0 -1
  152. package/src/components/PageContainer/PageContainer.stories.ts +170 -2
  153. package/src/components/PageContainer/PageContainer.vue +63 -8
  154. package/src/components/PageContainer/tests/__snapshots__/PageContainer.spec.ts.snap +19 -11
  155. package/src/components/PaginatedTable/PaginatedTable.mdx +0 -1
  156. package/src/components/PeriodField/PeriodField.mdx +0 -1
  157. package/src/components/PhoneField/PhoneField.mdx +2 -3
  158. package/src/components/PhoneField/PhoneField.stories.ts +227 -410
  159. package/src/components/PhoneField/PhoneField.vue +204 -438
  160. package/src/components/PhoneField/indicatifs.ts +1 -1
  161. package/src/components/PhoneField/locales.ts +7 -0
  162. package/src/components/PhoneField/tests/PhoneField.a11y.spec.ts +0 -1
  163. package/src/components/PhoneField/tests/PhoneField.spec.ts +517 -220
  164. package/src/components/PhoneField/types.ts +30 -0
  165. package/src/components/PhoneField/usePhoneFieldValidation.ts +119 -0
  166. package/src/components/PhoneField/usePhoneIndicatifs.ts +89 -0
  167. package/src/components/PhoneField/validation/validation.stories.ts +717 -0
  168. package/src/components/RangeField/RangeField.mdx +0 -1
  169. package/src/components/RatingPicker/RatingPicker.mdx +0 -1
  170. package/src/components/SocialMediaLinks/SocialMediaLinks.mdx +0 -1
  171. package/src/components/StatusPage/StatusPage.vue +1 -0
  172. package/src/components/StatusPage/tests/__snapshots__/StatusPage.spec.ts.snap +248 -230
  173. package/src/components/SubHeader/SubHeader.mdx +5 -6
  174. package/src/components/Tables/common/tests/SyTableFilter.spec.ts +11 -12
  175. package/src/components/UploadWorkflow/UploadWorkflow.mdx +0 -1
  176. package/src/components/UserMenuBtn/UserMenuBtn.mdx +0 -1
  177. package/src/components/UserMenuBtn/UserMenuBtn.stories.ts +177 -0
  178. package/src/composables/unifyValidation/documentationValidationProps.ts +1 -1
  179. package/src/composables/unifyValidation/tests/useValidation.spec.ts +13 -1
  180. package/src/composables/unifyValidation/useValidation.ts +37 -33
  181. package/src/composantsVuetify/VCard/VCard.mdx +4 -0
  182. package/src/composantsVuetify/VCard/v-card.stories.ts +93 -1
  183. package/src/composantsVuetify/VCarousel/VCarousel.mdx +74 -0
  184. package/src/composantsVuetify/VCarousel/v-carousel.stories.ts +531 -0
  185. package/src/composantsVuetify/VNavigationDrawer/VNavgationDrawer.mdx +53 -0
  186. package/src/composantsVuetify/VNavigationDrawer/v-navigation-drawer.stories.ts +310 -0
  187. package/src/composantsVuetify/VSlideGroup/VSlideGroup.mdx +105 -0
  188. package/src/composantsVuetify/VSlideGroup/v-slide-group.stories.ts +463 -0
  189. package/src/designTokens/tokens/baseColors.ts +1 -1
  190. package/src/designTokens/tokens/baseTokens.ts +18 -18
  191. package/src/stories/Components/Components.stories.ts +34 -1
  192. package/src/stories/Demarrer/Releases.stories.ts +16 -2
  193. package/src/stories/DesignTokens/Arrondis.mdx +1 -1
  194. package/src/stories/DesignTokens/Correspondances.mdx +219 -0
  195. package/src/stories/DesignTokens/UtiliserLesTokens.mdx +235 -0
  196. package/src/stories/DesignTokens/colors.stories.ts +569 -569
  197. package/src/stories/GuideDuDev/Amelipro.stories.ts +335 -267
  198. package/dist/components/LunarCalendar/useLunarCalendarRules.d.ts +0 -5
  199. package/dist/components/PhoneField/tests/types.d.ts +0 -18
  200. package/src/components/LunarCalendar/tests/useLunarCalendarRules.spec.ts +0 -184
  201. package/src/components/LunarCalendar/useLunarCalendarRules.ts +0 -96
  202. package/src/components/PhoneField/tests/types.d.ts +0 -19
@@ -9,4 +9,5 @@ export const locales = {
9
9
  yearBtnLabelUnselected: (selectedYear: string) => `Sélectionner une année, nous sommes actuellement en ${selectedYear}`,
10
10
  monthBtnLabelSelected: (selectedMonth: string) => `Sélectionner un mois, le mois sélectionné est ${selectedMonth}`,
11
11
  monthBtnLabelUnselected: (selectedMonth: string) => `Sélectionner un mois, nous sommes actuellement en ${selectedMonth}`,
12
+ fieldRequired: (label?: string) => `${label ? `Le champ ${label}` : 'Ce champ'} est requis.`,
12
13
  }
@@ -84,6 +84,8 @@ describe('mounthpicker', () => {
84
84
 
85
85
  expect(wrapper.find('input').element.value).toBe('11/2025')
86
86
 
87
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
88
+ // @ts-ignore - Vue Test Utils cannot infer props from complex intersection type
87
89
  await wrapper.setProps({ modelValue: '12/2026' })
88
90
  expect(wrapper.find('input').element.value).toBe('12/2026')
89
91
 
@@ -434,6 +436,8 @@ describe('mounthpicker', () => {
434
436
  await yearButton.trigger('keydown', { key: 'ArrowRight' })
435
437
  expect(yearSelector.find('.year-2025').attributes('tabindex')).toBe('0')
436
438
 
439
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
440
+ // @ts-ignore - Vue Test Utils cannot infer props from complex intersection type
437
441
  await wrapper.setProps({ yearsOrder: 'asc' })
438
442
  expect(yearSelector.find('.year-2025').attributes('tabindex')).toBe('0')
439
443
  await yearButton.trigger('keydown', { key: 'ArrowUp' })
@@ -473,6 +477,8 @@ describe('mounthpicker', () => {
473
477
  expect(yearButtons[0]!.text()).toBe('2100')
474
478
  expect(yearButtons[yearButtons.length - 1]!.text()).toBe('1900')
475
479
 
480
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
481
+ // @ts-ignore - Vue Test Utils cannot infer props from complex intersection type
476
482
  await wrapper.setProps({ yearsOrder: 'asc' })
477
483
 
478
484
  const newYearButtons = wrapper.findComponent({ name: 'YearSelector' }).findAll('.year-selector__year')
@@ -1162,7 +1168,7 @@ describe('mounthpicker', () => {
1162
1168
  options: {
1163
1169
  validate: (value: string) => {
1164
1170
  const regex = /^(0[1-9]|1[0-2])\/\d{4}$/
1165
- return regex.test(value) || 'Invalid month/year format. Use MM/YYYY.'
1171
+ return regex.test(value)
1166
1172
  },
1167
1173
  message: 'Invalid month/year format. Use MM/YYYY.',
1168
1174
  },
@@ -1171,6 +1177,7 @@ describe('mounthpicker', () => {
1171
1177
  })
1172
1178
 
1173
1179
  const input = wrapper.find('input')
1180
+ await input.trigger('focus')
1174
1181
  await input.setValue('99/2025')
1175
1182
  await input.trigger('blur')
1176
1183
 
@@ -1189,7 +1196,7 @@ describe('mounthpicker', () => {
1189
1196
  options: {
1190
1197
  validate: (value: string) => {
1191
1198
  const regex = /^(0[1-9]|1[0-2])\/\d{4}$/
1192
- return regex.test(value) || 'Invalid month/year format. Use MM/YYYY.'
1199
+ return regex.test(value)
1193
1200
  },
1194
1201
  message: 'Invalid month/year format. Use MM/YYYY.',
1195
1202
  },
@@ -1239,11 +1246,355 @@ describe('mounthpicker', () => {
1239
1246
  const yearButton = wrapper.findComponent({ name: 'YearSelector' }).find('.year-2025')
1240
1247
  await yearButton.trigger('click')
1241
1248
  await flushPromises()
1249
+ await (wrapper.vm as unknown as { validateOnSubmit: () => Promise<boolean> }).validateOnSubmit()
1250
+ await flushPromises()
1242
1251
 
1243
1252
  expect(wrapper.find('.v-field--error').exists()).toBe(true)
1244
1253
  expect(wrapper.find('.v-input__details').text()).toBe('The year must be 2026 or later.')
1245
1254
 
1246
1255
  wrapper.unmount()
1247
1256
  })
1257
+
1258
+ it('shows error when required field is left empty on blur', async () => {
1259
+ const wrapper = mount(MonthPicker, {
1260
+ props: {
1261
+ label: 'Début du projet',
1262
+ required: true,
1263
+ },
1264
+ })
1265
+ const vm = wrapper.vm as unknown as { errors: string[] }
1266
+
1267
+ const input = wrapper.find('input')
1268
+ await input.trigger('focus')
1269
+ await wrapper.vm.$nextTick()
1270
+ await input.trigger('blur')
1271
+ await wrapper.vm.$nextTick()
1272
+ await flushPromises()
1273
+ await wrapper.vm.$nextTick()
1274
+
1275
+ expect(vm.errors).toContain('Le champ Début du projet est requis.')
1276
+
1277
+ wrapper.unmount()
1278
+ })
1279
+
1280
+ it('validateOnSubmit returns false for empty required field and true for valid value', async () => {
1281
+ const emptyWrapper = mount(MonthPicker, {
1282
+ props: { modelValue: '', required: true, label: 'Début du projet' },
1283
+ })
1284
+ const emptyVm = emptyWrapper.vm as unknown as { validateOnSubmit: () => Promise<boolean>, errors: string[] }
1285
+ const result = await emptyVm.validateOnSubmit()
1286
+ expect(result).toBe(false)
1287
+ expect(emptyVm.errors).toContain('Le champ Début du projet est requis.')
1288
+ emptyWrapper.unmount()
1289
+
1290
+ const validWrapper = mount(MonthPicker, {
1291
+ props: { modelValue: '03/2026', required: true, label: 'Début du projet' },
1292
+ })
1293
+ const validVm = validWrapper.vm as unknown as { validateOnSubmit: () => Promise<boolean> }
1294
+ const validResult = await validVm.validateOnSubmit()
1295
+ expect(validResult).toBe(true)
1296
+ validWrapper.unmount()
1297
+ })
1298
+
1299
+ it('displays external errorMessages, warningMessages, successMessages injected by the parent', async () => {
1300
+ const errorWrapper = mount(MonthPicker, {
1301
+ props: {
1302
+ label: 'Début du projet',
1303
+ modelValue: '03/2026',
1304
+ errorMessages: ['Ce mois est déjà utilisé'],
1305
+ },
1306
+ })
1307
+ const errorMsg = errorWrapper.findAll('.v-messages__message')
1308
+ expect(errorMsg[0]?.text()).toBe('Ce mois est déjà utilisé')
1309
+ errorWrapper.unmount()
1310
+
1311
+ const warningWrapper = mount(MonthPicker, {
1312
+ props: {
1313
+ label: 'Début du projet',
1314
+ modelValue: '03/2026',
1315
+ warningMessages: ['Ce mois est proche d\'une échéance'],
1316
+ },
1317
+ })
1318
+ const warningMsg = warningWrapper.findAll('.v-messages__message')
1319
+ expect(warningMsg[0]?.text()).toBe('Ce mois est proche d\'une échéance')
1320
+ warningWrapper.unmount()
1321
+
1322
+ const successWrapper = mount(MonthPicker, {
1323
+ props: {
1324
+ label: 'Début du projet',
1325
+ modelValue: '03/2026',
1326
+ successMessages: ['Mois accepté'],
1327
+ showSuccessMessages: true,
1328
+ },
1329
+ })
1330
+ const successMsg = successWrapper.findAll('.v-messages__message')
1331
+ expect(successMsg[0]?.text()).toBe('Mois accepté')
1332
+ successWrapper.unmount()
1333
+ })
1334
+
1335
+ it('handles customWarningRules and customSuccessRules', async () => {
1336
+ async function mountAndBlur(modelValue: string) {
1337
+ const wrapper = mount(MonthPicker, {
1338
+ props: {
1339
+ label: 'Début du projet',
1340
+ modelValue,
1341
+ customRules: [{
1342
+ type: 'custom',
1343
+ options: {
1344
+ message: 'Le format doit être MM/YYYY.',
1345
+ validate: (value: string) => /^(0[1-9]|1[0-2])\/\d{4}$/.test(value),
1346
+ },
1347
+ }],
1348
+ customWarningRules: [{
1349
+ type: 'custom',
1350
+ options: {
1351
+ warningMessage: 'La date est dans le passé.',
1352
+ validate: (value: string) => {
1353
+ const [month, year] = value.split('/').map(Number) as [number, number]
1354
+ const now = new Date()
1355
+ return year > now.getFullYear() || (year === now.getFullYear() && month >= now.getMonth() + 1)
1356
+ },
1357
+ },
1358
+ }],
1359
+ customSuccessRules: [{
1360
+ type: 'custom',
1361
+ options: {
1362
+ successMessage: 'Date valide.',
1363
+ validate: (value: string) => /^(0[1-9]|1[0-2])\/\d{4}$/.test(value),
1364
+ },
1365
+ }],
1366
+ showSuccessMessages: true,
1367
+ },
1368
+ })
1369
+ await wrapper.find('input').trigger('focus')
1370
+ await wrapper.vm.$nextTick()
1371
+ await wrapper.find('input').trigger('blur')
1372
+ await wrapper.vm.$nextTick()
1373
+ await flushPromises()
1374
+ await wrapper.vm.$nextTick()
1375
+ return wrapper
1376
+ }
1377
+
1378
+ const errorWrapper = await mountAndBlur('99/2025')
1379
+ expect((errorWrapper.vm as unknown as { errors: string[] }).errors).toContain('Le format doit être MM/YYYY.')
1380
+ errorWrapper.unmount()
1381
+
1382
+ const warningWrapper = await mountAndBlur('01/2020')
1383
+ const warningMessages = warningWrapper.findAll('.v-messages__message')
1384
+ expect(warningMessages[0]?.text()).toBe('La date est dans le passé.')
1385
+ warningWrapper.unmount()
1386
+
1387
+ const successWrapper = await mountAndBlur('03/2030')
1388
+ expect((successWrapper.vm as unknown as { successes: string[] }).successes).toContain('Date valide.')
1389
+ successWrapper.unmount()
1390
+ })
1391
+
1392
+ it('sets hasError, hasWarning, hasSuccess based on validation state', async () => {
1393
+ async function mountAndBlur(modelValue: string) {
1394
+ const wrapper = mount(MonthPicker, {
1395
+ props: {
1396
+ label: 'Début du projet',
1397
+ modelValue,
1398
+ customRules: [{
1399
+ type: 'custom',
1400
+ options: {
1401
+ message: 'Le format doit être MM/YYYY.',
1402
+ validate: (value: string) => /^(0[1-9]|1[0-2])\/\d{4}$/.test(value),
1403
+ },
1404
+ }],
1405
+ customWarningRules: [{
1406
+ type: 'custom',
1407
+ options: {
1408
+ warningMessage: 'La date est dans le passé.',
1409
+ validate: (value: string) => {
1410
+ const [month, year] = value.split('/').map(Number) as [number, number]
1411
+ const now = new Date()
1412
+ return year > now.getFullYear() || (year === now.getFullYear() && month >= now.getMonth() + 1)
1413
+ },
1414
+ },
1415
+ }],
1416
+ customSuccessRules: [{
1417
+ type: 'custom',
1418
+ options: {
1419
+ successMessage: 'Date valide.',
1420
+ validate: (value: string) => /^(0[1-9]|1[0-2])\/\d{4}$/.test(value),
1421
+ },
1422
+ }],
1423
+ },
1424
+ })
1425
+ await wrapper.find('input').trigger('focus')
1426
+ await wrapper.vm.$nextTick()
1427
+ await wrapper.find('input').trigger('blur')
1428
+ await wrapper.vm.$nextTick()
1429
+ await flushPromises()
1430
+ await wrapper.vm.$nextTick()
1431
+ return wrapper.vm as unknown as { hasError: boolean, hasWarning: boolean, hasSuccess: boolean }
1432
+ }
1433
+
1434
+ expect((await mountAndBlur('99/2025')).hasError).toBe(true)
1435
+ expect((await mountAndBlur('01/2020')).hasWarning).toBe(true)
1436
+ expect((await mountAndBlur('03/2030')).hasSuccess).toBe(true)
1437
+ })
1438
+
1439
+ it('does not show errors when disableErrorHandling is true', async () => {
1440
+ const wrapper = mount(MonthPicker, {
1441
+ props: {
1442
+ label: 'Début du projet',
1443
+ required: true,
1444
+ disableErrorHandling: true,
1445
+ },
1446
+ })
1447
+
1448
+ const input = wrapper.find('input')
1449
+ await input.trigger('focus')
1450
+ await wrapper.vm.$nextTick()
1451
+ await input.trigger('blur')
1452
+ await wrapper.vm.$nextTick()
1453
+ await flushPromises()
1454
+ await wrapper.vm.$nextTick()
1455
+
1456
+ expect(wrapper.find('.v-field--error').exists()).toBe(false)
1457
+ expect((wrapper.vm as unknown as { errors: string[] }).errors).toHaveLength(0)
1458
+
1459
+ wrapper.unmount()
1460
+ })
1461
+
1462
+ it('validates on input when isValidateOnBlur is false', async () => {
1463
+ const wrapper = mount(MonthPicker, {
1464
+ props: {
1465
+ label: 'Début du projet',
1466
+ isValidateOnBlur: false,
1467
+ customRules: [{
1468
+ type: 'custom',
1469
+ options: {
1470
+ message: 'Le format doit être MM/YYYY.',
1471
+ validate: (value: string) => /^(0[1-9]|1[0-2])\/\d{4}$/.test(value),
1472
+ },
1473
+ }],
1474
+ },
1475
+ })
1476
+
1477
+ const input = wrapper.find('input')
1478
+ await input.setValue('99/2025')
1479
+ await wrapper.vm.$nextTick()
1480
+ await flushPromises()
1481
+ await wrapper.vm.$nextTick()
1482
+
1483
+ expect(wrapper.find('.v-field--error').exists()).toBe(true)
1484
+ expect((wrapper.vm as unknown as { errors: string[] }).errors).toContain('Le format doit être MM/YYYY.')
1485
+
1486
+ wrapper.unmount()
1487
+ })
1488
+
1489
+ it('sets aria-required from the required prop alone (without displaying the asterisk)', async () => {
1490
+ const wrapper = mount(MonthPicker, {
1491
+ props: {
1492
+ label: 'Début du projet',
1493
+ required: true,
1494
+ },
1495
+ })
1496
+
1497
+ const input = wrapper.find('input')
1498
+ expect(input.attributes('aria-required')).toBe('true')
1499
+ // L'astérisque ne s'affiche pas tant que displayAsterisk n'est pas activé
1500
+ expect(wrapper.find('label').text()).not.toContain('*')
1501
+
1502
+ wrapper.unmount()
1503
+ })
1504
+
1505
+ it('displays the asterisk only when both required and displayAsterisk are true', async () => {
1506
+ const wrapper = mount(MonthPicker, {
1507
+ props: {
1508
+ label: 'Début du projet',
1509
+ required: true,
1510
+ displayAsterisk: true,
1511
+ },
1512
+ })
1513
+
1514
+ expect(wrapper.find('label').text()).toContain('*')
1515
+ expect(wrapper.find('input').attributes('aria-required')).toBe('true')
1516
+
1517
+ wrapper.unmount()
1518
+
1519
+ // displayAsterisk sans required : pas d'astérisque
1520
+ const asteriskOnlyWrapper = mount(MonthPicker, {
1521
+ props: {
1522
+ label: 'Début du projet',
1523
+ displayAsterisk: true,
1524
+ },
1525
+ })
1526
+ expect(asteriskOnlyWrapper.find('label').text()).not.toContain('*')
1527
+ asteriskOnlyWrapper.unmount()
1528
+ })
1529
+
1530
+ it('does not set aria-required nor asterisk by default', async () => {
1531
+ const wrapper = mount(MonthPicker, {
1532
+ props: {
1533
+ label: 'Début du projet',
1534
+ },
1535
+ })
1536
+
1537
+ const input = wrapper.find('input')
1538
+ expect(input.attributes('aria-required')).toBeUndefined()
1539
+ expect(wrapper.find('label').text()).not.toContain('*')
1540
+
1541
+ wrapper.unmount()
1542
+ })
1543
+
1544
+ it('displays the validation state icon (error / warning / success)', async () => {
1545
+ async function mountAndBlur(modelValue: string) {
1546
+ const wrapper = mount(MonthPicker, {
1547
+ props: {
1548
+ label: 'Début du projet',
1549
+ modelValue,
1550
+ showSuccessMessages: true,
1551
+ customRules: [{
1552
+ type: 'custom',
1553
+ options: {
1554
+ message: 'Le format doit être MM/YYYY.',
1555
+ validate: (value: string) => /^(0[1-9]|1[0-2])\/\d{4}$/.test(value),
1556
+ },
1557
+ }],
1558
+ customWarningRules: [{
1559
+ type: 'custom',
1560
+ options: {
1561
+ warningMessage: 'La date est dans le passé.',
1562
+ validate: (value: string) => {
1563
+ const [month, year] = value.split('/').map(Number) as [number, number]
1564
+ const now = new Date()
1565
+ return year > now.getFullYear() || (year === now.getFullYear() && month >= now.getMonth() + 1)
1566
+ },
1567
+ },
1568
+ }],
1569
+ customSuccessRules: [{
1570
+ type: 'custom',
1571
+ options: {
1572
+ successMessage: 'Date valide.',
1573
+ validate: (value: string) => /^(0[1-9]|1[0-2])\/\d{4}$/.test(value),
1574
+ },
1575
+ }],
1576
+ },
1577
+ })
1578
+ await wrapper.find('input').trigger('focus')
1579
+ await wrapper.vm.$nextTick()
1580
+ await wrapper.find('input').trigger('blur')
1581
+ await wrapper.vm.$nextTick()
1582
+ await flushPromises()
1583
+ await wrapper.vm.$nextTick()
1584
+ return wrapper
1585
+ }
1586
+
1587
+ const errorWrapper = await mountAndBlur('99/2025')
1588
+ expect(errorWrapper.find('.field-state-icon.error-icon').exists()).toBe(true)
1589
+ errorWrapper.unmount()
1590
+
1591
+ const warningWrapper = await mountAndBlur('01/2020')
1592
+ expect(warningWrapper.find('.field-state-icon.warning-icon').exists()).toBe(true)
1593
+ warningWrapper.unmount()
1594
+
1595
+ const successWrapper = await mountAndBlur('03/2030')
1596
+ expect(successWrapper.find('.field-state-icon.success-icon').exists()).toBe(true)
1597
+ successWrapper.unmount()
1598
+ })
1248
1599
  })
1249
1600
  })
@@ -2169,7 +2169,7 @@ exports[`mounthpicker > should open the menu when clicking on the input 1`] = `
2169
2169
  <div class="visual-picker-header">
2170
2170
  <div
2171
2171
  class="visual-picker-header__title"
2172
- id="v-4-title"
2172
+ id="v-6-title"
2173
2173
  >
2174
2174
  Sélectionner un mois
2175
2175
  </div>
@@ -2396,6 +2396,7 @@ exports[`mounthpicker > should render mounthpicker 1`] = `
2396
2396
  <div class="sy-textfield-container">
2397
2397
  <div class="
2398
2398
  basic-field
2399
+ help-text-as-hint
2399
2400
  v-input
2400
2401
  v-input--center-affix
2401
2402
  v-input--density-default
@@ -2439,20 +2440,21 @@ exports[`mounthpicker > should render mounthpicker 1`] = `
2439
2440
  v-field-label
2440
2441
  v-label
2441
2442
  "
2442
- id="input-v-2-label"
2443
+ id="input-v-4-label"
2443
2444
  >
2444
2445
  <!---->
2445
2446
  Début du projet
2446
2447
  </label>
2447
2448
  <!---->
2448
2449
  <input
2449
- aria-describedby="input-v-2-messages"
2450
+ aria-describedby="input-v-4-messages"
2450
2451
  aria-label="Début du projet"
2451
- aria-labelledby="input-v-2-label"
2452
+ aria-labelledby="input-v-4-label"
2452
2453
  autocomplete="off"
2453
2454
  class="v-field__input"
2454
2455
  direction="horizontal"
2455
- id="input-v-2"
2456
+ id="input-v-4"
2457
+ name="v-1"
2456
2458
  size="1"
2457
2459
  title="Début du projet"
2458
2460
  type="text"
@@ -2479,7 +2481,7 @@ exports[`mounthpicker > should render mounthpicker 1`] = `
2479
2481
  v-field-label--floating
2480
2482
  v-label
2481
2483
  "
2482
- for="input-v-2"
2484
+ for="input-v-4"
2483
2485
  >
2484
2486
  <!---->
2485
2487
  Début du projet
@@ -2523,7 +2525,7 @@ exports[`mounthpicker > should render mounthpicker 1`] = `
2523
2525
  <div
2524
2526
  aria-live="polite"
2525
2527
  class="v-input__details"
2526
- id="input-v-2-messages"
2528
+ id="input-v-4-messages"
2527
2529
  role="alert"
2528
2530
  >
2529
2531
  <transition-group-stub
@@ -2534,7 +2536,9 @@ exports[`mounthpicker > should render mounthpicker 1`] = `
2534
2536
  persisted="false"
2535
2537
  tag="div"
2536
2538
  >
2537
- <!---->
2539
+ <div class="v-messages__message">
2540
+ Format MM/AAAA
2541
+ </div>
2538
2542
  </transition-group-stub>
2539
2543
  <!---->
2540
2544
  </div>
@@ -0,0 +1,16 @@
1
+ import { type TextFieldProps } from './MonthPickerText/useTextField'
2
+ import { type MonthPickerVisualProps } from './MonthPickerVisual/MonthPickerVisualProps'
3
+ import { type FieldValidationProps } from '@/composables/unifyValidation/useValidation'
4
+ import { locales as defaultLocales } from './locales'
5
+
6
+ export type MonthPickerProps =
7
+ TextFieldProps
8
+ & FieldValidationProps
9
+ & Partial<MonthPickerVisualProps>
10
+ & {
11
+ modelValue?: string
12
+ locales?: typeof defaultLocales
13
+ disabled?: boolean
14
+ readonly?: boolean
15
+ displayAsterisk?: boolean
16
+ }
@@ -1,30 +1,67 @@
1
- import { type ValidationRule } from '@/composables/validation/useValidation'
2
- import { computed } from 'vue'
1
+ import type { ValidationRule as SyValidationRule } from '@/composables/validation/useValidation'
2
+ import { useValidation } from '@/composables/unifyValidation/useValidation'
3
+ import { computed, type Ref } from 'vue'
4
+ import type { ValidationRule as VuetifyValidationRule } from 'vuetify'
5
+ import type { locales } from './locales'
3
6
 
4
- export type ValidationProps = {
5
- customRules?: ValidationRule[]
6
- customWarningRules?: ValidationRule[]
7
- customSuccessRules?: ValidationRule[]
8
- errorMessages?: string[] | null
9
- warningMessages?: string[] | null
10
- successMessages?: string[] | null
11
- hasError?: boolean
12
- hasWarning?: boolean
13
- hasSuccess?: boolean
14
- showSuccessMessages?: boolean
15
- }
7
+ export function useMonthPickerValidation(args: {
8
+ modelValue: Ref<unknown>
9
+ readonly: Ref<boolean>
10
+ disabled: Ref<boolean>
11
+ required: Ref<boolean>
12
+ isValidateOnBlur: Ref<boolean>
13
+ showSuccessMessages: Ref<boolean>
14
+ disableErrorHandling: Ref<boolean>
15
+ useVuetifyValidation: Ref<boolean>
16
+ label: Ref<string | undefined>
17
+ rules: Ref<VuetifyValidationRule[] | undefined>
18
+ customRules: Ref<SyValidationRule[]>
19
+ customWarningRules?: Ref<SyValidationRule[]>
20
+ customSuccessRules?: Ref<SyValidationRule[]>
21
+ errorMessages?: Ref<string[] | null | undefined>
22
+ warningMessages?: Ref<string[] | null | undefined>
23
+ successMessages?: Ref<string[] | null | undefined>
24
+ hasErrorProp?: Ref<boolean>
25
+ hasWarningProp?: Ref<boolean>
26
+ hasSuccessProp?: Ref<boolean>
27
+ maxErrors?: Ref<number>
28
+ focused: Ref<boolean>
29
+ locales: Ref<typeof locales>
30
+ }) {
31
+ const allCustomRules = computed<SyValidationRule[]>(() => {
32
+ const base: SyValidationRule[] = args.required.value
33
+ ? [{
34
+ type: 'required',
35
+ options: {
36
+ message: args.locales.value.fieldRequired(args.label.value),
37
+ fieldIdentifier: args.label.value,
38
+ },
39
+ }]
40
+ : []
41
+ return [...base, ...(args.customRules.value ?? [])]
42
+ })
16
43
 
17
- export function useMonthPickerValidation(props: ValidationProps) {
18
- return computed(() => ({
19
- customRules: props.customRules,
20
- customWarningRules: props.customWarningRules,
21
- customSuccessRules: props.customSuccessRules,
22
- errorMessages: props.errorMessages,
23
- warningMessages: props.warningMessages,
24
- successMessages: props.successMessages,
25
- hasError: props.hasError,
26
- hasWarning: props.hasWarning,
27
- hasSuccess: props.hasSuccess,
28
- showSuccessMessages: props.showSuccessMessages,
29
- }))
44
+ return useValidation({
45
+ modelValue: args.modelValue,
46
+ readonly: args.readonly,
47
+ disabled: args.disabled,
48
+ required: args.required,
49
+ isValidateOnBlur: args.isValidateOnBlur,
50
+ showSuccessMessages: args.showSuccessMessages,
51
+ disableErrorHandling: args.disableErrorHandling,
52
+ useVuetifyValidation: args.useVuetifyValidation,
53
+ label: args.label,
54
+ rules: args.rules,
55
+ customRules: allCustomRules,
56
+ customWarningRules: args.customWarningRules,
57
+ customSuccessRules: args.customSuccessRules,
58
+ errorMessages: args.errorMessages,
59
+ warningMessages: args.warningMessages,
60
+ successMessages: args.successMessages,
61
+ hasErrorProp: args.hasErrorProp,
62
+ hasWarningProp: args.hasWarningProp,
63
+ hasSuccessProp: args.hasSuccessProp,
64
+ maxErrors: args.maxErrors,
65
+ focused: args.focused,
66
+ })
30
67
  }